diff --git a/CHANGELOG.md b/CHANGELOG.md index 303c23aef5..d17c69b4e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,8 @@ Up requirements to Django 3.2+, Twisted 21+ subfolders. All imports will need to be updated. - Made `MonitorHandler.add/remove` support `category` for monitoring Attributes with a category (before only key was used, ignoring category entirely). +- Move `create_*` functions into db managers, leaving `utils.create` only being + wrapper functions (consistent with `utils.search`). No change of api otherwise. ### Evennia 0.9.5 (2019-2020) diff --git a/evennia/accounts/manager.py b/evennia/accounts/manager.py index b193ef3729..ccb00c0ce1 100644 --- a/evennia/accounts/manager.py +++ b/evennia/accounts/manager.py @@ -3,9 +3,12 @@ The managers for the custom Account object and permissions. """ import datetime +from django.conf import settings from django.utils import timezone from django.contrib.auth.models import UserManager from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager +from evennia.server import signals +from evennia.utils.utils import make_iter, class_from_module, dbid_to_obj __all__ = ("AccountManager", "AccountDBManager") @@ -181,6 +184,108 @@ class AccountDBManager(TypedObjectManager, UserManager): ) return matches + def create_account( + self, + key, + email, + password, + typeclass=None, + is_superuser=False, + locks=None, + permissions=None, + tags=None, + attributes=None, + report_to=None, + ): + """ + This creates a new account. + + Args: + key (str): The account's name. This should be unique. + email (str or None): Email on valid addr@addr.domain form. If + the empty string, will be set to None. + password (str): Password in cleartext. + + Keyword Args: + typeclass (str): The typeclass to use for the account. + is_superuser (bool): Wether or not this account is to be a superuser + locks (str): Lockstring. + permission (list): List of permission strings. + tags (list): List of Tags on form `(key, category[, data])` + attributes (list): List of Attributes on form + `(key, value [, category, [,lockstring [, default_pass]]])` + report_to (Object): An object with a msg() method to report + errors to. If not given, errors will be logged. + + Returns: + Account: The newly created Account. + Raises: + ValueError: If `key` already exists in database. + + + Notes: + Usually only the server admin should need to be superuser, all + other access levels can be handled with more fine-grained + permissions or groups. A superuser bypasses all lock checking + operations and is thus not suitable for play-testing the game. + + """ + typeclass = typeclass if typeclass else settings.BASE_ACCOUNT_TYPECLASS + locks = make_iter(locks) if locks is not None else None + permissions = make_iter(permissions) if permissions is not None else None + tags = make_iter(tags) if tags is not None else None + attributes = make_iter(attributes) if attributes is not None else None + + if isinstance(typeclass, str): + # a path is given. Load the actual typeclass. + typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) + + # setup input for the create command. We use AccountDB as baseclass + # here to give us maximum freedom (the typeclasses will load + # correctly when each object is recovered). + + if not email: + email = None + if self.model.objects.filter(username__iexact=key): + raise ValueError("An Account with the name '%s' already exists." % key) + + # this handles a given dbref-relocate to an account. + report_to = dbid_to_obj(report_to, self.model) + + # create the correct account entity, using the setup from + # base django auth. + now = timezone.now() + email = typeclass.objects.normalize_email(email) + new_account = typeclass( + username=key, + email=email, + is_staff=is_superuser, + is_superuser=is_superuser, + last_login=now, + date_joined=now, + ) + if password is not None: + # the password may be None for 'fake' accounts, like bots + valid, error = new_account.validate_password(password, new_account) + if not valid: + raise error + + new_account.set_password(password) + + new_account._createdict = dict( + locks=locks, permissions=permissions, + report_to=report_to, tags=tags, attributes=attributes + ) + # saving will trigger the signal that calls the + # at_first_save hook on the typeclass, where the _createdict + # can be used. + new_account.save() + + # note that we don't send a signal here, that is sent from the Account.create helper method + # instead. + + return new_account + # back-compatibility alias account_search = search_account diff --git a/evennia/comms/managers.py b/evennia/comms/managers.py index 2da708af9c..2c65455dc5 100644 --- a/evennia/comms/managers.py +++ b/evennia/comms/managers.py @@ -5,10 +5,12 @@ Comm system components. """ +from django.conf import settings from django.db.models import Q from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager +from evennia.server import signals from evennia.utils import logger -from evennia.utils.utils import dbref +from evennia.utils.utils import dbref, make_iter, class_from_module _GA = object.__getattribute__ _AccountDB = None @@ -287,6 +289,55 @@ class MsgManager(TypedObjectManager): # back-compatibility alias message_search = search_message + 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)`. + 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 @@ -388,6 +439,56 @@ class ChannelDBManager(TypedObjectManager): ).distinct() return channels + def create_channel( + self, key, aliases=None, desc=None, locks=None, keep_log=True, typeclass=None, tags=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)`. + + 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 + ) + + # 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 diff --git a/evennia/help/manager.py b/evennia/help/manager.py index 48c7f68e30..3d660ab114 100644 --- a/evennia/help/manager.py +++ b/evennia/help/manager.py @@ -1,8 +1,11 @@ """ Custom manager for HelpEntry objects. """ +from django.db import IntegrityError from evennia.utils import logger, utils from evennia.typeclasses.managers import TypedObjectManager +from evennia.utils.utils import make_iter +from evennia.server import signals __all__ = ("HelpEntryManager",) @@ -149,3 +152,46 @@ class HelpEntryManager(TypedObjectManager): return self.filter(db_key__iexact=ostring, db_help_category__iexact=help_category) else: return self.filter(db_key__iexact=ostring) + + def create_help(self, key, entrytext, category="General", locks=None, aliases=None, tags=None): + """ + Create a static help entry in the help database. Note that Command + help entries are dynamic and directly taken from the __doc__ + entries of the command. The database-stored help entries are + intended for more general help on the game, more extensive info, + in-game setting information and so on. + + Args: + key (str): The name of the help entry. + entrytext (str): The body of te help entry + category (str, optional): The help category of the entry. + locks (str, optional): A lockstring to restrict access. + aliases (list of str, optional): List of alternative (likely shorter) keynames. + tags (lst, optional): List of tags or tuples `(tag, category)`. + + Returns: + help (HelpEntry): A newly created help entry. + + """ + try: + new_help = self.model() + new_help.key = key + new_help.entrytext = entrytext + new_help.help_category = category + if locks: + new_help.locks.add(locks) + if aliases: + new_help.aliases.add(make_iter(aliases)) + if tags: + new_help.tags.batch_add(*tags) + new_help.save() + return new_help + except IntegrityError: + string = "Could not add help entry: key '%s' already exists." % key + logger.log_err(string) + return None + except Exception: + logger.log_trace() + return None + + signals.SIGNAL_HELPENTRY_POST_CREATE.send(sender=new_help) diff --git a/evennia/objects/manager.py b/evennia/objects/manager.py index 49a5473f80..4fa534fb0e 100644 --- a/evennia/objects/manager.py +++ b/evennia/objects/manager.py @@ -7,6 +7,9 @@ from django.conf import settings from django.db.models.fields import exceptions from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager from evennia.utils.utils import is_iter, make_iter, string_partial_matching +from evennia.utils.utils import class_from_module, dbid_to_obj +from evennia.server import signals + __all__ = ("ObjectManager", "ObjectDBManager") _GA = object.__getattribute__ @@ -595,6 +598,117 @@ class ObjectDBManager(TypedObjectManager): """ self.filter(db_sessid__isnull=False).update(db_sessid=None) + def create_object( + self, + typeclass=None, + key=None, + location=None, + home=None, + permissions=None, + locks=None, + aliases=None, + tags=None, + destination=None, + report_to=None, + nohome=False, + attributes=None, + nattributes=None, + ): + """ + + Create a new in-game object. + + Keyword Args: + typeclass (class or str): Class or python path to a typeclass. + key (str): Name of the new object. If not set, a name of + `#dbref` will be set. + location (Object or str): Obj or #dbref to use as the location of the new object. + home (Object or str): Obj or #dbref to use as the object's home location. + permissions (list): A list of permission strings or tuples (permstring, category). + locks (str): one or more lockstrings, separated by semicolons. + aliases (list): A list of alternative keys or tuples (aliasstring, category). + tags (list): List of tag keys or tuples (tagkey, category) or (tagkey, category, data). + destination (Object or str): Obj or #dbref to use as an Exit's target. + report_to (Object): The object to return error messages to. + nohome (bool): This allows the creation of objects without a + default home location; only used when creating the default + location itself or during unittests. + attributes (list): Tuples on the form (key, value) or (key, value, category), + (key, value, lockstring) or (key, value, lockstring, default_access). + to set as Attributes on the new object. + nattributes (list): Non-persistent tuples on the form (key, value). Note that + adding this rarely makes sense since this data will not survive a reload. + + Returns: + object (Object): A newly created object of the given typeclass. + + Raises: + ObjectDB.DoesNotExist: If trying to create an Object with + `location` or `home` that can't be found. + + """ + typeclass = typeclass if typeclass else settings.BASE_OBJECT_TYPECLASS + + # convenience converters to avoid common usage mistake + permissions = make_iter(permissions) if permissions is not None else None + locks = make_iter(locks) if locks is not None else None + aliases = make_iter(aliases) if aliases is not None else None + tags = make_iter(tags) if tags is not None else None + attributes = make_iter(attributes) if attributes is not None else None + + if isinstance(typeclass, str): + # a path is given. Load the actual typeclass + typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) + + # Setup input for the create command. We use ObjectDB as baseclass here + # to give us maximum freedom (the typeclasses will load + # correctly when each object is recovered). + + location = dbid_to_obj(location, self.model) + destination = dbid_to_obj(destination, self.model) + home = dbid_to_obj(home, self.model) + if not home: + try: + home = dbid_to_obj(settings.DEFAULT_HOME, self.model) if not nohome else None + except self.model_ObjectDB.DoesNotExist: + raise self.model.DoesNotExist( + "settings.DEFAULT_HOME (= '%s') does not exist, or the setting is malformed." + % settings.DEFAULT_HOME + ) + + # create new instance + new_object = typeclass( + db_key=key, + db_location=location, + db_destination=destination, + db_home=home, + db_typeclass_path=typeclass.path, + ) + # store the call signature for the signal + new_object._createdict = dict( + key=key, + location=location, + destination=destination, + home=home, + typeclass=typeclass.path, + permissions=permissions, + locks=locks, + aliases=aliases, + tags=tags, + report_to=report_to, + nohome=nohome, + attributes=attributes, + nattributes=nattributes, + ) + # 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_object.save() + + signals.SIGNAL_OBJECT_POST_CREATE.send(sender=new_object) + + return new_object + class ObjectManager(ObjectDBManager, TypeclassManager): pass diff --git a/evennia/scripts/manager.py b/evennia/scripts/manager.py index 6a8579fc12..900fdd45da 100644 --- a/evennia/scripts/manager.py +++ b/evennia/scripts/manager.py @@ -2,13 +2,19 @@ The custom manager for Scripts. """ +from django.conf import settings from django.db.models import Q from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager -from evennia.utils.utils import make_iter +from evennia.utils.utils import make_iter, class_from_module, dbid_to_obj +from evennia.server import signals __all__ = ("ScriptManager", "ScriptDBManager") _GA = object.__getattribute__ +_ObjectDB = None +_AccountDB = None + + VALIDATE_ITERATION = 0 @@ -81,7 +87,6 @@ class ScriptDBManager(TypedObjectManager): """ if key: - script = [] dbref = self.dbref(key) if dbref: return self.filter(id=dbref) @@ -191,6 +196,129 @@ class ScriptDBManager(TypedObjectManager): ) return new_script + def create_script( + self, + typeclass=None, + key=None, + obj=None, + account=None, + locks=None, + interval=None, + start_delay=None, + repeats=None, + persistent=None, + autostart=True, + report_to=None, + desc=None, + tags=None, + attributes=None, + ): + """ + Create a new script. All scripts are a combination of a database + object that communicates with the database, and an typeclass that + 'decorates' the database object into being different types of + scripts. It's behaviour is similar to the game objects except + scripts has a time component and are more limited in scope. + + Keyword Args: + typeclass (class or str): Class or python path to a typeclass. + key (str): Name of the new object. If not set, a name of + #dbref will be set. + obj (Object): The entity on which this Script sits. If this + is `None`, we are creating a "global" script. + account (Account): The account on which this Script sits. It is + exclusiv to `obj`. + locks (str): one or more lockstrings, separated by semicolons. + interval (int): The triggering interval for this Script, in + seconds. If unset, the Script will not have a timing + component. + start_delay (bool): If `True`, will wait `interval` seconds + before triggering the first time. + repeats (int): The number of times to trigger before stopping. + If unset, will repeat indefinitely. + persistent (bool): If this Script survives a server shutdown + or not (all Scripts will survive a reload). + autostart (bool): If this Script will start immediately when + created or if the `start` method must be called explicitly. + report_to (Object): The object to return error messages to. + desc (str): Optional description of script + tags (list): List of tags or tuples (tag, category). + attributes (list): List if tuples (key, value) or (key, value, category) + (key, value, lockstring) or (key, value, lockstring, default_access). + + Returns: + script (obj): An instance of the script created + + See evennia.scripts.manager for methods to manipulate existing + scripts in the database. + + """ + global _ObjectDB, _AccountDB + if not _ObjectDB: + from evennia.objects.models import ObjectDB as _ObjectDB + from evennia.accounts.models import AccountDB as _AccountDB + + typeclass = typeclass if typeclass else settings.BASE_SCRIPT_TYPECLASS + + if isinstance(typeclass, str): + # a path is given. Load the actual typeclass + typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) + + # validate input + kwarg = {} + if key: + kwarg["db_key"] = key + if account: + kwarg["db_account"] = dbid_to_obj(account, _AccountDB) + if obj: + kwarg["db_obj"] = dbid_to_obj(obj, _ObjectDB) + if interval: + kwarg["db_interval"] = max(0, interval) + if start_delay: + kwarg["db_start_delay"] = start_delay + if repeats: + kwarg["db_repeats"] = max(0, repeats) + if persistent: + kwarg["db_persistent"] = persistent + if desc: + kwarg["db_desc"] = desc + tags = make_iter(tags) if tags is not None else None + attributes = make_iter(attributes) if attributes is not None else None + + # create new instance + new_script = typeclass(**kwarg) + + # store the call signature for the signal + new_script._createdict = dict( + key=key, + obj=obj, + account=account, + locks=locks, + interval=interval, + start_delay=start_delay, + repeats=repeats, + persistent=persistent, + autostart=autostart, + report_to=report_to, + desc=desc, + tags=tags, + attributes=attributes, + ) + # 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_script.save() + + if not new_script.id: + # this happens in the case of having a repeating script with `repeats=1` and + # `start_delay=False` - the script will run once and immediately stop before + # save is over. + return None + + signals.SIGNAL_SCRIPT_POST_CREATE.send(sender=new_script) + + return new_script + class ScriptManager(ScriptDBManager, TypeclassManager): pass diff --git a/evennia/utils/create.py b/evennia/utils/create.py index 78a0739171..19a5c4c5b3 100644 --- a/evennia/utils/create.py +++ b/evennia/utils/create.py @@ -13,26 +13,9 @@ The respective object managers hold more methods for manipulating and searching objects already existing in the database. """ -from django.conf import settings -from django.db import IntegrityError -from django.utils import timezone -from evennia.utils import logger -from evennia.server import signals -from evennia.utils.utils import make_iter, class_from_module, dbid_to_obj - -# delayed imports -_User = None -_ObjectDB = None -_Object = None -_Script = None -_ScriptDB = None -_HelpEntry = None -_Msg = None -_Account = None -_AccountDB = None -_to_object = None -_ChannelDB = None +from django.db.utils import OperationalError +from django.contrib.contenttypes.models import ContentType # limit symbol import from API __all__ = ( @@ -46,125 +29,60 @@ __all__ = ( _GA = object.__getattribute__ +# import objects this way to avoid circular import problems +try: + ObjectDB = ContentType.objects.get(app_label="objects", model="objectdb").model_class() + ScriptDB = ContentType.objects.get(app_label="scripts", model="scriptdb").model_class() + AccountDB = ContentType.objects.get(app_label="accounts", model="accountdb").model_class() + Msg = ContentType.objects.get(app_label="comms", model="msg").model_class() + ChannelDB = ContentType.objects.get(app_label="comms", model="channeldb").model_class() + HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_class() + Tag = ContentType.objects.get(app_label="typeclasses", model="tag").model_class() +except OperationalError: + # this is a fallback used during tests/doc building + print("Couldn't initialize create managers - db not set up.") + from evennia.objects.models import ObjectDB + from evennia.accounts.models import AccountDB + from evennia.scripts.models import ScriptDB + from evennia.comms.models import Msg, ChannelDB + from evennia.help.models import HelpEntry + from evennia.typeclasses.tags import Tag # noqa + # # Game Object creation +# +# Create a new in-game object. +# +# Keyword Args: +# typeclass (class or str): Class or python path to a typeclass. +# key (str): Name of the new object. If not set, a name of +# `#dbref` will be set. +# location (Object or str): Obj or #dbref to use as the location of the new object. +# home (Object or str): Obj or #dbref to use as the object's home location. +# permissions (list): A list of permission strings or tuples (permstring, category). +# locks (str): one or more lockstrings, separated by semicolons. +# aliases (list): A list of alternative keys or tuples (aliasstring, category). +# tags (list): List of tag keys or tuples (tagkey, category) or (tagkey, category, data). +# destination (Object or str): Obj or #dbref to use as an Exit's target. +# report_to (Object): The object to return error messages to. +# nohome (bool): This allows the creation of objects without a +# default home location; only used when creating the default +# location itself or during unittests. +# attributes (list): Tuples on the form (key, value) or (key, value, category), +# (key, value, lockstring) or (key, value, lockstring, default_access). +# to set as Attributes on the new object. +# nattributes (list): Non-persistent tuples on the form (key, value). Note that +# adding this rarely makes sense since this data will not survive a reload. +# +# Returns: +# object (Object): A newly created object of the given typeclass. +# +# Raises: +# ObjectDB.DoesNotExist: If trying to create an Object with +# `location` or `home` that can't be found. +# - -def create_object( - typeclass=None, - key=None, - location=None, - home=None, - permissions=None, - locks=None, - aliases=None, - tags=None, - destination=None, - report_to=None, - nohome=False, - attributes=None, - nattributes=None, -): - """ - - Create a new in-game object. - - Keyword Args: - typeclass (class or str): Class or python path to a typeclass. - key (str): Name of the new object. If not set, a name of - `#dbref` will be set. - location (Object or str): Obj or #dbref to use as the location of the new object. - home (Object or str): Obj or #dbref to use as the object's home location. - permissions (list): A list of permission strings or tuples (permstring, category). - locks (str): one or more lockstrings, separated by semicolons. - aliases (list): A list of alternative keys or tuples (aliasstring, category). - tags (list): List of tag keys or tuples (tagkey, category) or (tagkey, category, data). - destination (Object or str): Obj or #dbref to use as an Exit's target. - report_to (Object): The object to return error messages to. - nohome (bool): This allows the creation of objects without a - default home location; only used when creating the default - location itself or during unittests. - attributes (list): Tuples on the form (key, value) or (key, value, category), - (key, value, lockstring) or (key, value, lockstring, default_access). - to set as Attributes on the new object. - nattributes (list): Non-persistent tuples on the form (key, value). Note that - adding this rarely makes sense since this data will not survive a reload. - - Returns: - object (Object): A newly created object of the given typeclass. - - Raises: - ObjectDB.DoesNotExist: If trying to create an Object with - `location` or `home` that can't be found. - - """ - global _ObjectDB - if not _ObjectDB: - from evennia.objects.models import ObjectDB as _ObjectDB - - typeclass = typeclass if typeclass else settings.BASE_OBJECT_TYPECLASS - - # convenience converters to avoid common usage mistake - permissions = make_iter(permissions) if permissions is not None else None - locks = make_iter(locks) if locks is not None else None - aliases = make_iter(aliases) if aliases is not None else None - tags = make_iter(tags) if tags is not None else None - attributes = make_iter(attributes) if attributes is not None else None - - if isinstance(typeclass, str): - # a path is given. Load the actual typeclass - typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) - - # Setup input for the create command. We use ObjectDB as baseclass here - # to give us maximum freedom (the typeclasses will load - # correctly when each object is recovered). - - location = dbid_to_obj(location, _ObjectDB) - destination = dbid_to_obj(destination, _ObjectDB) - home = dbid_to_obj(home, _ObjectDB) - if not home: - try: - home = dbid_to_obj(settings.DEFAULT_HOME, _ObjectDB) if not nohome else None - except _ObjectDB.DoesNotExist: - raise _ObjectDB.DoesNotExist( - "settings.DEFAULT_HOME (= '%s') does not exist, or the setting is malformed." - % settings.DEFAULT_HOME - ) - - # create new instance - new_object = typeclass( - db_key=key, - db_location=location, - db_destination=destination, - db_home=home, - db_typeclass_path=typeclass.path, - ) - # store the call signature for the signal - new_object._createdict = dict( - key=key, - location=location, - destination=destination, - home=home, - typeclass=typeclass.path, - permissions=permissions, - locks=locks, - aliases=aliases, - tags=tags, - report_to=report_to, - nohome=nohome, - attributes=attributes, - nattributes=nattributes, - ) - # 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_object.save() - - signals.SIGNAL_OBJECT_POST_CREATE.send(sender=new_object) - - return new_object - - +create_object = ObjectDB.objects.create_object # alias for create_object object = create_object @@ -172,128 +90,45 @@ object = create_object # # Script creation +# Create a new script. All scripts are a combination of a database +# object that communicates with the database, and an typeclass that +# 'decorates' the database object into being different types of +# scripts. It's behaviour is similar to the game objects except +# scripts has a time component and are more limited in scope. +# +# Keyword Args: +# typeclass (class or str): Class or python path to a typeclass. +# key (str): Name of the new object. If not set, a name of +# #dbref will be set. +# obj (Object): The entity on which this Script sits. If this +# is `None`, we are creating a "global" script. +# account (Account): The account on which this Script sits. It is +# exclusiv to `obj`. +# locks (str): one or more lockstrings, separated by semicolons. +# interval (int): The triggering interval for this Script, in +# seconds. If unset, the Script will not have a timing +# component. +# start_delay (bool): If `True`, will wait `interval` seconds +# before triggering the first time. +# repeats (int): The number of times to trigger before stopping. +# If unset, will repeat indefinitely. +# persistent (bool): If this Script survives a server shutdown +# or not (all Scripts will survive a reload). +# autostart (bool): If this Script will start immediately when +# created or if the `start` method must be called explicitly. +# report_to (Object): The object to return error messages to. +# desc (str): Optional description of script +# tags (list): List of tags or tuples (tag, category). +# attributes (list): List if tuples (key, value) or (key, value, category) +# (key, value, lockstring) or (key, value, lockstring, default_access). +# +# Returns: +# script (obj): An instance of the script created +# +# See evennia.scripts.manager for methods to manipulate existing +# scripts in the database. -def create_script( - typeclass=None, - key=None, - obj=None, - account=None, - locks=None, - interval=None, - start_delay=None, - repeats=None, - persistent=None, - autostart=True, - report_to=None, - desc=None, - tags=None, - attributes=None, -): - """ - Create a new script. All scripts are a combination of a database - object that communicates with the database, and an typeclass that - 'decorates' the database object into being different types of - scripts. It's behaviour is similar to the game objects except - scripts has a time component and are more limited in scope. - - Keyword Args: - typeclass (class or str): Class or python path to a typeclass. - key (str): Name of the new object. If not set, a name of - #dbref will be set. - obj (Object): The entity on which this Script sits. If this - is `None`, we are creating a "global" script. - account (Account): The account on which this Script sits. It is - exclusiv to `obj`. - locks (str): one or more lockstrings, separated by semicolons. - interval (int): The triggering interval for this Script, in - seconds. If unset, the Script will not have a timing - component. - start_delay (bool): If `True`, will wait `interval` seconds - before triggering the first time. - repeats (int): The number of times to trigger before stopping. - If unset, will repeat indefinitely. - persistent (bool): If this Script survives a server shutdown - or not (all Scripts will survive a reload). - autostart (bool): If this Script will start immediately when - created or if the `start` method must be called explicitly. - report_to (Object): The object to return error messages to. - desc (str): Optional description of script - tags (list): List of tags or tuples (tag, category). - attributes (list): List if tuples (key, value) or (key, value, category) - (key, value, lockstring) or (key, value, lockstring, default_access). - - Returns: - script (obj): An instance of the script created - - See evennia.scripts.manager for methods to manipulate existing - scripts in the database. - - """ - global _ScriptDB - if not _ScriptDB: - from evennia.scripts.models import ScriptDB as _ScriptDB - - typeclass = typeclass if typeclass else settings.BASE_SCRIPT_TYPECLASS - - if isinstance(typeclass, str): - # a path is given. Load the actual typeclass - typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) - - # validate input - kwarg = {} - if key: - kwarg["db_key"] = key - if account: - kwarg["db_account"] = dbid_to_obj(account, _AccountDB) - if obj: - kwarg["db_obj"] = dbid_to_obj(obj, _ObjectDB) - if interval: - kwarg["db_interval"] = max(0, interval) - if start_delay: - kwarg["db_start_delay"] = start_delay - if repeats: - kwarg["db_repeats"] = max(0, repeats) - if persistent: - kwarg["db_persistent"] = persistent - if desc: - kwarg["db_desc"] = desc - tags = make_iter(tags) if tags is not None else None - attributes = make_iter(attributes) if attributes is not None else None - - # create new instance - new_script = typeclass(**kwarg) - - # store the call signature for the signal - new_script._createdict = dict( - key=key, - obj=obj, - account=account, - locks=locks, - interval=interval, - start_delay=start_delay, - repeats=repeats, - persistent=persistent, - autostart=autostart, - report_to=report_to, - desc=desc, - tags=tags, - attributes=attributes, - ) - # 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_script.save() - - if not new_script.id: - # this happens in the case of having a repeating script with `repeats=1` and - # `start_delay=False` - the script will run once and immediately stop before save is over. - return None - - signals.SIGNAL_SCRIPT_POST_CREATE.send(sender=new_script) - - return new_script - - +create_script = ScriptDB.objects.create_script # alias script = create_script @@ -302,55 +137,26 @@ script = create_script # Help entry creation # +# """ +# Create a static help entry in the help database. Note that Command +# help entries are dynamic and directly taken from the __doc__ +# entries of the command. The database-stored help entries are +# intended for more general help on the game, more extensive info, +# in-game setting information and so on. +# +# Args: +# key (str): The name of the help entry. +# entrytext (str): The body of te help entry +# category (str, optional): The help category of the entry. +# locks (str, optional): A lockstring to restrict access. +# aliases (list of str, optional): List of alternative (likely shorter) keynames. +# tags (lst, optional): List of tags or tuples `(tag, category)`. +# +# Returns: +# help (HelpEntry): A newly created help entry. +# -def create_help_entry(key, entrytext, category="General", locks=None, aliases=None, tags=None): - """ - Create a static help entry in the help database. Note that Command - help entries are dynamic and directly taken from the __doc__ - entries of the command. The database-stored help entries are - intended for more general help on the game, more extensive info, - in-game setting information and so on. - - Args: - key (str): The name of the help entry. - entrytext (str): The body of te help entry - category (str, optional): The help category of the entry. - locks (str, optional): A lockstring to restrict access. - aliases (list of str, optional): List of alternative (likely shorter) keynames. - tags (lst, optional): List of tags or tuples `(tag, category)`. - - Returns: - help (HelpEntry): A newly created help entry. - - """ - global _HelpEntry - if not _HelpEntry: - from evennia.help.models import HelpEntry as _HelpEntry - - try: - new_help = _HelpEntry() - new_help.key = key - new_help.entrytext = entrytext - new_help.help_category = category - if locks: - new_help.locks.add(locks) - if aliases: - new_help.aliases.add(make_iter(aliases)) - if tags: - new_help.tags.batch_add(*tags) - new_help.save() - return new_help - except IntegrityError: - string = "Could not add help entry: key '%s' already exists." % key - logger.log_err(string) - return None - except Exception: - logger.log_trace() - return None - - signals.SIGNAL_HELPENTRY_POST_CREATE.send(sender=new_help) - - +create_help_entry = HelpEntry.objects.create_help # alias help_entry = create_help_entry @@ -358,117 +164,59 @@ help_entry = create_help_entry # # Comm system methods +# +# 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)`. +# 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. +# -def create_message( - 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)`. - 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." - ) - - global _Msg - if not _Msg: - from evennia.comms.models import Msg as _Msg - if not message: - # we don't allow empty messages. - return None - new_message = _Msg(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 - - +create_message = Msg.objects.create_message message = create_message create_msg = create_message -def create_channel( - key, aliases=None, desc=None, locks=None, keep_log=True, typeclass=None, tags=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)`. - - 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 - ) - - # 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 - +# 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)`. +# +# Returns: +# channel (Channel): A newly created channel. +# +create_channel = ChannelDB.objects.create_channel channel = create_channel @@ -476,111 +224,37 @@ channel = create_channel # Account creation methods # +# This creates a new account. +# +# Args: +# key (str): The account's name. This should be unique. +# email (str or None): Email on valid addr@addr.domain form. If +# the empty string, will be set to None. +# password (str): Password in cleartext. +# +# Keyword Args: +# typeclass (str): The typeclass to use for the account. +# is_superuser (bool): Wether or not this account is to be a superuser +# locks (str): Lockstring. +# permission (list): List of permission strings. +# tags (list): List of Tags on form `(key, category[, data])` +# attributes (list): List of Attributes on form +# `(key, value [, category, [,lockstring [, default_pass]]])` +# report_to (Object): An object with a msg() method to report +# errors to. If not given, errors will be logged. +# +# Returns: +# Account: The newly created Account. +# Raises: +# ValueError: If `key` already exists in database. +# +# +# Notes: +# Usually only the server admin should need to be superuser, all +# other access levels can be handled with more fine-grained +# permissions or groups. A superuser bypasses all lock checking +# operations and is thus not suitable for play-testing the game. -def create_account( - key, - email, - password, - typeclass=None, - is_superuser=False, - locks=None, - permissions=None, - tags=None, - attributes=None, - report_to=None, -): - """ - This creates a new account. - - Args: - key (str): The account's name. This should be unique. - email (str or None): Email on valid addr@addr.domain form. If - the empty string, will be set to None. - password (str): Password in cleartext. - - Keyword Args: - typeclass (str): The typeclass to use for the account. - is_superuser (bool): Wether or not this account is to be a superuser - locks (str): Lockstring. - permission (list): List of permission strings. - tags (list): List of Tags on form `(key, category[, data])` - attributes (list): List of Attributes on form - `(key, value [, category, [,lockstring [, default_pass]]])` - report_to (Object): An object with a msg() method to report - errors to. If not given, errors will be logged. - - Returns: - Account: The newly created Account. - Raises: - ValueError: If `key` already exists in database. - - - Notes: - Usually only the server admin should need to be superuser, all - other access levels can be handled with more fine-grained - permissions or groups. A superuser bypasses all lock checking - operations and is thus not suitable for play-testing the game. - - """ - global _AccountDB - if not _AccountDB: - from evennia.accounts.models import AccountDB as _AccountDB - - typeclass = typeclass if typeclass else settings.BASE_ACCOUNT_TYPECLASS - locks = make_iter(locks) if locks is not None else None - permissions = make_iter(permissions) if permissions is not None else None - tags = make_iter(tags) if tags is not None else None - attributes = make_iter(attributes) if attributes is not None else None - - if isinstance(typeclass, str): - # a path is given. Load the actual typeclass. - typeclass = class_from_module(typeclass, settings.TYPECLASS_PATHS) - - # setup input for the create command. We use AccountDB as baseclass - # here to give us maximum freedom (the typeclasses will load - # correctly when each object is recovered). - - if not email: - email = None - if _AccountDB.objects.filter(username__iexact=key): - raise ValueError("An Account with the name '%s' already exists." % key) - - # this handles a given dbref-relocate to an account. - report_to = dbid_to_obj(report_to, _AccountDB) - - # create the correct account entity, using the setup from - # base django auth. - now = timezone.now() - email = typeclass.objects.normalize_email(email) - new_account = typeclass( - username=key, - email=email, - is_staff=is_superuser, - is_superuser=is_superuser, - last_login=now, - date_joined=now, - ) - if password is not None: - # the password may be None for 'fake' accounts, like bots - valid, error = new_account.validate_password(password, new_account) - if not valid: - raise error - - new_account.set_password(password) - - new_account._createdict = dict( - locks=locks, permissions=permissions, report_to=report_to, tags=tags, attributes=attributes - ) - # saving will trigger the signal that calls the - # at_first_save hook on the typeclass, where the _createdict - # can be used. - new_account.save() - - # note that we don't send a signal here, that is sent from the Account.create helper method - # instead. - - return new_account - - +create_account = AccountDB.objects.create_account # alias account = create_account diff --git a/evennia/utils/search.py b/evennia/utils/search.py index 787b81bbea..63dd7fedab 100644 --- a/evennia/utils/search.py +++ b/evennia/utils/search.py @@ -110,7 +110,7 @@ except OperationalError: # candidates=None, # attribute_name=None): # -search_object = ObjectDB.objects.object_search +search_object = ObjectDB.objects.search_object search_objects = search_object object_search = search_object objects = search_objects @@ -126,7 +126,7 @@ objects = search_objects # ostring = a string or database id. # -search_account = AccountDB.objects.account_search +search_account = AccountDB.objects.search_account search_accounts = search_account account_search = search_account accounts = search_accounts @@ -144,7 +144,7 @@ accounts = search_accounts # on a timer. # -search_script = ScriptDB.objects.script_search +search_script = ScriptDB.objects.search_script search_scripts = search_script script_search = search_script scripts = search_scripts @@ -165,7 +165,7 @@ scripts = search_scripts # one of the other arguments to limit the search. # -search_message = Msg.objects.message_search +search_message = Msg.objects.search_message search_messages = search_message message_search = search_message messages = search_messages @@ -181,7 +181,7 @@ messages = search_messages # exact - requires an exact ostring match (not case sensitive) # -search_channel = ChannelDB.objects.channel_search +search_channel = ChannelDB.objects.search_channel search_channels = search_channel channel_search = search_channel channels = search_channels