Homogenize manager search methods to return querysets. Resolve #2384.

This commit is contained in:
Griatch 2022-01-09 17:13:24 +01:00
parent 01af303457
commit 9c0b44e13a
7 changed files with 65 additions and 39 deletions

View file

@ -139,6 +139,7 @@ Up requirements to Django 3.2+, Twisted 21+
- Add support for `$dbref()` and `$search` when assigning an Attribute value
with the `set` command. This allows assigning real objects from in-game.
- Add ability to examine `/script` and `/channel` entities with `examine` command.
- Homogenize manager search methods to return querysets and not lists.
### Evennia 0.9.5 (2019-2020)

View file

@ -156,14 +156,17 @@ class AccountDBManager(TypedObjectManager, UserManager):
(non-case-sensitive fuzzy match).
typeclass (str or Typeclass, optional): Limit the search only to
accounts of this typeclass.
Returns:
Queryset: A queryset (an iterable) with 0, 1 or more matches.
"""
dbref = self.dbref(ostring)
if dbref or dbref == 0:
# bref search is always exact
matches = self.filter(id=dbref)
if matches:
return matches
# dbref search is always exact
dbref_match = self.search_dbref(dbref)
if dbref_match:
return dbref_match
query = {"username__iexact" if exact else "username__icontains": ostring}
if typeclass:
# we accept both strings and actual typeclasses

View file

@ -237,7 +237,7 @@ class MsgManager(TypedObjectManager):
always gives only one match.
Returns:
Queryset: Message matches.
Queryset: Iterable with 0, 1 or more matches.
"""
# unique msg id
@ -420,13 +420,16 @@ class ChannelDBManager(TypedObjectManager):
exact (bool, optional): Require an exact (but not
case sensitive) match.
Returns:
Queryset: Iterable with 0, 1 or more matches.
"""
dbref = self.dbref(ostring)
if dbref:
try:
return [self.get(id=dbref)]
except self.model.DoesNotExist:
pass
dbref_match = self.search_dbref(dbref)
if dbref_match:
return dbref_match
if exact:
channels = self.filter(
Q(db_key__iexact=ostring)

View file

@ -146,6 +146,9 @@ class HelpEntryManager(TypedObjectManager):
ostring (str): The help topic to look for.
category (str): Limit the search to a particular help topic
Returns:
Queryset: An iterable with 0, 1 or more matches.
"""
ostring = ostring.strip().lower()
if help_category:

View file

@ -32,7 +32,7 @@ class ObjectDBManager(TypedObjectManager):
Querysets or database objects).
dbref (converter)
get_id (alias: dbref_search)
dbref_search
get_dbref_range
object_totals
typeclass_search
@ -162,8 +162,8 @@ class ObjectDBManager(TypedObjectManager):
typeclasses (list, optional): Python pats to restrict matches with.
Returns:
matches (query): Objects fullfilling both the `attribute_name` and
`attribute_value` criterions.
Queryset: Iterable with 0, 1 or more matches fullfilling both the `attribute_name` and
`attribute_value` criterions.
Notes:
This uses the Attribute's PickledField to transparently search the database by matching
@ -222,6 +222,9 @@ class ObjectDBManager(TypedObjectManager):
candidates (list, optional): List of objects to limit search to.
typeclasses (list, optional): List of typeclass-path strings to restrict matches with
Returns:
Queryset: Iterable with 0, 1 or more matches.
"""
if isinstance(property_name, str):
if not property_name.startswith("db_"):
@ -234,11 +237,10 @@ class ObjectDBManager(TypedObjectManager):
)
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
try:
return list(
self.filter(cand_restriction & type_restriction & Q(**querykwargs)).order_by("id")
)
return self.filter(
cand_restriction & type_restriction & Q(**querykwargs)).order_by("id")
except exceptions.FieldError:
return []
return self.none()
except ValueError:
from evennia.utils import logger
@ -246,7 +248,7 @@ class ObjectDBManager(TypedObjectManager):
"The property '%s' does not support search criteria of the type %s."
% (property_name, type(property_value))
)
return []
return self.none()
def get_contents(self, location, excludeobj=None):
"""
@ -258,7 +260,8 @@ class ObjectDBManager(TypedObjectManager):
to exclude from the match.
Returns:
contents (query): Matching contents, without excludeobj, if given.
Queryset: Iterable with 0, 1 or more matches.
"""
exclude_restriction = (
Q(pk__in=[_GA(obj, "id") for obj in make_iter(excludeobj)]) if excludeobj else Q()
@ -276,17 +279,18 @@ class ObjectDBManager(TypedObjectManager):
typeclasses (list): Only match objects with typeclasses having thess path strings.
Returns:
matches (query): A list of matches of length 0, 1 or more.
Queryset: An iterable with 0, 1 or more matches.
"""
if not isinstance(ostring, str):
if hasattr(ostring, "key"):
ostring = ostring.key
else:
return []
return self.none()
if is_iter(candidates) and not len(candidates):
# if candidates is an empty iterable there can be no matches
# Exit early.
return []
return self.none()
# build query objects
candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj]
@ -327,10 +331,12 @@ class ObjectDBManager(TypedObjectManager):
# fuzzy matching
key_strings = search_candidates.values_list("db_key", flat=True).order_by("id")
match_ids = []
index_matches = string_partial_matching(key_strings, ostring, ret_index=True)
if index_matches:
# a match by key
return [obj for ind, obj in enumerate(search_candidates) if ind in index_matches]
match_ids = [obj.id for ind, obj in enumerate(search_candidates)
if ind in index_matches]
else:
# match by alias rather than by key
search_candidates = search_candidates.filter(
@ -346,8 +352,10 @@ class ObjectDBManager(TypedObjectManager):
index_matches = string_partial_matching(alias_strings, ostring, ret_index=True)
if index_matches:
# it's possible to have multiple matches to the same Object, we must weed those out
return list({alias_candidates[ind] for ind in index_matches})
return []
match_ids = [alias_candidates[ind].id for ind in index_matches]
# TODO - not ideal to have to do a second lookup here, but we want to return a queryset
# rather than a list ... maybe the above queries can be improved.
return self.filter(id__in=match_ids)
# main search methods and helper functions
@ -422,7 +430,7 @@ class ObjectDBManager(TypedObjectManager):
)
if not searchdata and searchdata != 0:
return []
return self.none()
if typeclass:
# typeclass may also be a list
@ -450,10 +458,11 @@ class ObjectDBManager(TypedObjectManager):
# Easiest case - dbref matching (always exact)
dbref_match = self.dbref_search(dbref)
if dbref_match:
if not candidates or dbref_match in candidates:
return [dbref_match]
dmatch = dbref_match[0]
if not candidates or dmatch in candidates:
return dbref_match
else:
return []
return self.none()
# Search through all possibilities.
match_number = None
@ -478,15 +487,16 @@ class ObjectDBManager(TypedObjectManager):
# this indicates trying to get a single match with a match-number
# targeting some higher-number match (like 2-box when there is only
# one box in the room). This leads to a no-match.
matches = []
matches = self.none()
elif len(matches) > 1 and match_number is not None:
# multiple matches, but a number was given to separate them
if 0 <= match_number < len(matches):
# limit to one match
matches = [matches[match_number]]
# limit to one match (we still want a queryset back)
# TODO: Can we do this some other way and avoid a second lookup?
matches = self.filter(id=matches[match_number].id)
else:
# a number was given outside of range. This means a no-match.
matches = []
matches = self.none()
# return a list (possibly empty)
return matches

View file

@ -28,7 +28,7 @@ class ScriptDBManager(TypedObjectManager):
Querysets or database objects).
dbref (converter)
get_id (or dbref_search)
dbref_search
get_dbref_range
object_totals
typeclass_search
@ -138,6 +138,9 @@ class ScriptDBManager(TypedObjectManager):
on a timer.
typeclass (class or str): Typeclass or path to typeclass.
Returns:
Queryset: An iterable with 0, 1 or more results.
"""
ostring = ostring.strip()
@ -146,10 +149,10 @@ class ScriptDBManager(TypedObjectManager):
if dbref:
# this is a dbref, try to find the script directly
dbref_match = self.dbref_search(dbref)
if dbref_match and not (
(obj and obj != dbref_match.obj) or (only_timed and dbref_match.interval)
):
return [dbref_match]
if dbref_match:
dmatch = dbref_match[0]
if not (obj and obj != dmatch.obj) or (only_timed and dmatch.interval):
return dbref_match
if typeclass:
if callable(typeclass):

View file

@ -464,10 +464,13 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
dbref (str or int): The id to search for.
Returns:
object (TypedObject): The matched object.
Queryset: Queryset with 0 or 1 match.
"""
return self.get_id(dbref)
dbref = self.dbref(dbref, reqhash=False)
if dbref:
return self.filter(id=dbref)
return self.none()
def get_dbref_range(self, min_dbref=None, max_dbref=None):
"""