Source code for evennia.comms.managers

"""
These managers define helper methods for accessing the database from
Comm system components.

"""

from django.conf import settings
from django.db.models import Q

from evennia.server import signals
from evennia.typeclasses.managers import TypeclassManager, TypedObjectManager
from evennia.utils import logger
from evennia.utils.utils import class_from_module, dbref, make_iter

_GA = object.__getattribute__
_AccountDB = None
_ObjectDB = None
_ChannelDB = None
_ScriptDB = None
_SESSIONS = None

# error class


[docs]class CommError(Exception): """ Raised by comm system, to allow feedback to player when caught. """ pass
# # helper functions #
[docs]def identify_object(inp): """ Helper function. Identifies if an object is an account or an object; return its database model Args: inp (any): Entity to be idtified. Returns: identified (tuple): This is a tuple with (`inp`, identifier) where `identifier` is one of "account", "object", "channel", "string", "dbref" or None. """ if hasattr(inp, "__dbclass__"): clsname = inp.__dbclass__.__name__ if clsname == "AccountDB": return inp, "account" elif clsname == "ObjectDB": return inp, "object" elif clsname == "ChannelDB": return inp, "channel" elif clsname == "ScriptDB": return inp, "script" if isinstance(inp, str): return inp, "string" elif dbref(inp): return dbref(inp), "dbref" else: return inp, None
[docs]def to_object(inp, objtype="account"): """ Locates the object related to the given accountname or channel key. If input was already the correct object, return it. Args: inp (any): The input object/string objtype (str): Either 'account' or 'channel'. Returns: obj (object): The correct object related to `inp`. """ obj, typ = identify_object(inp) if typ == objtype: return obj if objtype == "account": if typ == "object": return obj.account if typ == "string": return _AccountDB.objects.get(user_username__iexact=obj) if typ == "dbref": return _AccountDB.objects.get(id=obj) logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp))) raise CommError() elif objtype == "object": if typ == "account": return obj.obj if typ == "string": return _ObjectDB.objects.get(db_key__iexact=obj) if typ == "dbref": return _ObjectDB.objects.get(id=obj) logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp))) raise CommError() elif objtype == "channel": if typ == "string": return _ChannelDB.objects.get(db_key__iexact=obj) if typ == "dbref": return _ChannelDB.objects.get(id=obj) logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp))) raise CommError() elif objtype == "script": if typ == "string": return _ScriptDB.objects.get(db_key__iexact=obj) if typ == "dbref": return _ScriptDB.objects.get(id=obj) logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp))) raise CommError() # an unknown return None
# # Msg manager #
[docs]class MsgManager(TypedObjectManager): """ This MsgManager implements methods for searching and manipulating Messages directly from the database. These methods will all return database objects (or QuerySets) directly. A Message represents one unit of communication, be it over a Channel or via some form of in-game mail system. Like an e-mail, it always has a sender and can have any number of receivers (some of which may be Channels). """
[docs] def identify_object(self, inp): """ Wrapper to identify_object if accessing via the manager directly. Args: inp (any): Entity to be idtified. Returns: identified (tuple): This is a tuple with (`inp`, identifier) where `identifier` is one of "account", "object", "channel", "string", "dbref" or None. """ return identify_object(inp)
[docs] def get_message_by_id(self, idnum): """ Retrieve message by its id. Args: idnum (int or str): The dbref to retrieve. Returns: message (Msg): The message. """ try: return self.get(id=self.dbref(idnum, reqhash=False)) except Exception: return None
[docs] def get_messages_by_sender(self, sender): """ Get all messages sent by one entity - this could be either a account or an object Args: sender (Account or Object): The sender of the message. Returns: QuerySet: Matching messages. Raises: CommError: For incorrect sender types. """ obj, typ = identify_object(sender) if typ == "account": return self.filter(db_sender_accounts=obj).exclude(db_hide_from_accounts=obj) elif typ == "object": return self.filter(db_sender_objects=obj).exclude(db_hide_from_objects=obj) elif typ == "script": return self.filter(db_sender_scripts=obj) else: raise CommError
[docs] def get_messages_by_receiver(self, recipient): """ Get all messages sent to one given recipient. Args: recipient (Object, Account or Channel): The recipient of the messages to search for. Returns: Queryset: Matching messages. Raises: CommError: If the `recipient` is not of a valid type. """ obj, typ = identify_object(recipient) if typ == "account": return self.filter(db_receivers_accounts=obj).exclude(db_hide_from_accounts=obj) elif typ == "object": return self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj) elif typ == "script": return self.filter(db_receivers_scripts=obj) else: raise CommError
[docs] def search_message(self, sender=None, receiver=None, freetext=None, dbref=None): """ Search the message database for particular messages. At least one of the arguments must be given to do a search. Args: sender (Object, Account or Script, optional): Get messages sent by a particular sender. receiver (Object, Account or Channel, optional): Get messages received by a certain account,object or channel freetext (str): Search for a text string in a message. NOTE: This can potentially be slow, so make sure to supply one of the other arguments to limit the search. dbref (int): The exact database id of the message. This will override all other search criteria since it's unique and always gives only one match. Returns: Queryset: Iterable with 0, 1 or more matches. """ # unique msg id if dbref: return self.objects.filter(id=dbref) # We use Q objects to gradually build up the query - this way we only # need to do one database lookup at the end rather than gradually # refining with multiple filter:s. Django Note: Q objects can be # combined with & and | (=AND,OR). ~ negates the queryset # filter by sender (we need __pk to avoid an error with empty Q() objects) sender, styp = identify_object(sender) if sender: spk = sender.pk if styp == "account": sender_restrict = Q(db_sender_accounts__pk=spk) & ~Q(db_hide_from_accounts__pk=spk) elif styp == "object": sender_restrict = Q(db_sender_objects__pk=spk) & ~Q(db_hide_from_objects__pk=spk) elif styp == "script": sender_restrict = Q(db_sender_scripts__pk=spk) else: sender_restrict = Q() # filter by receiver receiver, rtyp = identify_object(receiver) if receiver: rpk = receiver.pk if rtyp == "account": receiver_restrict = Q(db_receivers_accounts__pk=rpk) & ~Q(db_hide_from_accounts__pk=rpk) elif rtyp == "object": receiver_restrict = Q(db_receivers_objects__pk=rpk) & ~Q(db_hide_from_objects__pk=rpk) elif rtyp == "script": receiver_restrict = Q(db_receivers_scripts__pk=rpk) elif rtyp == "channel": raise DeprecationWarning( "Msg.objects.search don't accept channel recipients since " "Channels no longer accepts Msg objects." ) else: receiver_restrict = Q() # filter by full text if freetext: fulltext_restrict = Q(db_header__icontains=freetext) | Q(db_message__icontains=freetext) else: fulltext_restrict = Q() # execute the query return self.filter(sender_restrict & receiver_restrict & fulltext_restrict)
# back-compatibility alias message_search = search_message
[docs] def create_message( self, senderobj, message, receivers=None, locks=None, tags=None, header=None, **kwargs, ): """ Create a new communication Msg. Msgs represent a unit of database-persistent communication between entites. Args: senderobj (Object, Account, Script, str or list): The entity (or entities) sending the Msg. If a `str`, this is the id-string for an external sender type. message (str): Text with the message. Eventual headers, titles etc should all be included in this text string. Formatting will be retained. receivers (Object, Account, Script, str or list): An Account/Object to send to, or a list of them. If a string, it's an identifier for an external receiver. locks (str): Lock definition string. tags (list): A list of tags or tuples `(tag[,category[,data]])`. header (str): Mime-type or other optional information for the message Notes: The Comm system is created to be very open-ended, so it's fully possible to let a message both go several receivers at the same time, it's up to the command definitions to limit this as desired. """ if "channels" in kwargs: raise DeprecationWarning( "create_message() does not accept 'channel' kwarg anymore " "- channels no longer accept Msg objects." ) if not message: # we don't allow empty messages. return None new_message = self.model(db_message=message) new_message.save() for sender in make_iter(senderobj): new_message.senders = sender new_message.header = header for receiver in make_iter(receivers): new_message.receivers = receiver if locks: new_message.locks.add(locks) if tags: new_message.tags.batch_add(*tags) new_message.save() return new_message
# # Channel manager #
[docs]class ChannelDBManager(TypedObjectManager): """ This ChannelManager implements methods for searching and manipulating Channels directly from the database. These methods will all return database objects (or QuerySets) directly. A Channel is an in-game venue for communication. It's essentially representation of a re-sender: Users sends Messages to the Channel, and the Channel re-sends those messages to all users subscribed to the Channel. """
[docs] def get_all_channels(self): """ Get all channels. Returns: channels (list): All channels in game. """ return self.all()
[docs] def get_channel(self, channelkey): """ Return the channel object if given its key. Also searches its aliases. Args: channelkey (str): Channel key to search for. Returns: channel (Channel or None): A channel match. """ dbref = self.dbref(channelkey) if dbref: try: return self.get(id=dbref) except self.model.DoesNotExist: pass results = self.filter( Q(db_key__iexact=channelkey) | Q(db_tags__db_tagtype__iexact="alias", db_tags__db_key__iexact=channelkey) ).distinct() return results[0] if results else None
[docs] def get_subscriptions(self, subscriber): """ Return all channels a given entity is subscribed to. Args: subscriber (Object or Account): The one subscribing. Returns: subscriptions (list): Channel subscribed to. """ clsname = subscriber.__dbclass__.__name__ if clsname == "AccountDB": return subscriber.account_subscription_set.all() if clsname == "ObjectDB": return subscriber.object_subscription_set.all() return []
[docs] def search_channel(self, ostring, exact=True): """ Search the channel database for a particular channel. Args: ostring (str): The key or database id of the channel. 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: dbref_match = self.search_dbref(dbref) if dbref_match: return dbref_match if exact: channels = self.filter( Q(db_key__iexact=ostring) | Q(db_tags__db_tagtype__iexact="alias", db_tags__db_key__iexact=ostring) ).distinct() else: channels = self.filter( Q(db_key__icontains=ostring) | Q(db_tags__db_tagtype__iexact="alias", db_tags__db_key__icontains=ostring) ).distinct() return channels
[docs] def create_channel( self, key, aliases=None, desc=None, locks=None, keep_log=True, typeclass=None, tags=None, attrs=None, ): """ Create A communication Channel. A Channel serves as a central hub for distributing Msgs to groups of people without specifying the receivers explicitly. Instead accounts may 'connect' to the channel and follow the flow of messages. By default the channel allows access to all old messages, but this can be turned off with the keep_log switch. Args: key (str): This must be unique. Keyword Args: aliases (list of str): List of alternative (likely shorter) keynames. desc (str): A description of the channel, for use in listings. locks (str): Lockstring. keep_log (bool): Log channel throughput. typeclass (str or class): The typeclass of the Channel (not often used). tags (list): A list of tags or tuples `(tag[,category[,data]])`. attrs (list): List of attributes on form `(name, value[,category[,lockstring]])` Returns: channel (Channel): A newly created channel. """ typeclass = typeclass if typeclass else settings.BASE_CHANNEL_TYPECLASS if isinstance(typeclass, str): # a path is given. Load the actual typeclass typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) # create new instance new_channel = typeclass(db_key=key) # store call signature for the signal new_channel._createdict = dict( key=key, aliases=aliases, desc=desc, locks=locks, keep_log=keep_log, tags=tags, attrs=attrs, ) # this will trigger the save signal which in turn calls the # at_first_save hook on the typeclass, where the _createdict can be # used. new_channel.save() signals.SIGNAL_CHANNEL_POST_CREATE.send(sender=new_channel) return new_channel
# back-compatibility alias channel_search = search_channel
[docs]class ChannelManager(ChannelDBManager, TypeclassManager): """ Wrapper to group the typeclass manager to a consistent name. """ pass