Move create methods into managers, leave utils.create as wrappers.

Resolve #2118.
This commit is contained in:
Griatch 2022-01-09 00:28:11 +01:00
parent cc22a776ee
commit e70337b6d2
8 changed files with 692 additions and 522 deletions

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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