Resolve merge conflicts

This commit is contained in:
Griatch 2020-07-18 23:30:23 +02:00
commit 90e149dc27
14 changed files with 214 additions and 63 deletions

View file

@ -80,6 +80,9 @@ without arguments starts a full interactive Python console.
- Update Twisted requirement to >=2.3.0 to close security vulnerability - Update Twisted requirement to >=2.3.0 to close security vulnerability
- Add `$random` inlinefunc, supports minval,maxval arguments that can be ints and floats. - Add `$random` inlinefunc, supports minval,maxval arguments that can be ints and floats.
- Add `evennia.utils.inlinefuncs.raw(<str>)` as a helper to escape inlinefuncs in a string. - Add `evennia.utils.inlinefuncs.raw(<str>)` as a helper to escape inlinefuncs in a string.
- Make CmdGet/Drop/Give give proper error if `obj.move_to` returns `False`.
- Make `Object/Room/Exit.create`'s `account` argument optional. If not given, will set perms
to that of the object itself (along with normal Admin/Dev permission).
## Evennia 0.9 (2018-2019) ## Evennia 0.9 (2018-2019)

View file

@ -154,7 +154,8 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
if not account.is_superuser and ( if not account.is_superuser and (
account.db._playable_characters and len(account.db._playable_characters) >= charmax account.db._playable_characters and len(account.db._playable_characters) >= charmax
): ):
self.msg("You may only create a maximum of %i characters." % charmax) plural = "" if charmax == 1 else "s"
self.msg(f"You may only create a maximum of {charmax} character{plural}.")
return return
from evennia.objects.models import ObjectDB from evennia.objects.models import ObjectDB

View file

@ -426,7 +426,10 @@ class CmdGet(COMMAND_DEFAULT_CLASS):
if not obj.at_before_get(caller): if not obj.at_before_get(caller):
return return
obj.move_to(caller, quiet=True) success = obj.move_to(caller, quiet=True)
if not success:
caller.msg("This can't be picked up.")
else:
caller.msg("You pick up %s." % obj.name) caller.msg("You pick up %s." % obj.name)
caller.location.msg_contents("%s picks up %s." % (caller.name, obj.name), exclude=caller) caller.location.msg_contents("%s picks up %s." % (caller.name, obj.name), exclude=caller)
# calling at_get hook method # calling at_get hook method
@ -471,7 +474,10 @@ class CmdDrop(COMMAND_DEFAULT_CLASS):
if not obj.at_before_drop(caller): if not obj.at_before_drop(caller):
return return
obj.move_to(caller.location, quiet=True) success = obj.move_to(caller.location, quiet=True)
if not success:
caller.msg("This couldn't be dropped.")
else:
caller.msg("You drop %s." % (obj.name,)) caller.msg("You drop %s." % (obj.name,))
caller.location.msg_contents("%s drops %s." % (caller.name, obj.name), exclude=caller) caller.location.msg_contents("%s drops %s." % (caller.name, obj.name), exclude=caller)
# Call the object script's at_drop() method. # Call the object script's at_drop() method.
@ -522,8 +528,11 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
return return
# give object # give object
success = to_give.move_to(target, quiet=True)
if not success:
caller.msg("This could not be given.")
else:
caller.msg("You give %s to %s." % (to_give.key, target.key)) caller.msg("You give %s to %s." % (to_give.key, target.key))
to_give.move_to(target, quiet=True)
target.msg("%s gives you %s." % (caller.key, to_give.key)) target.msg("%s gives you %s." % (caller.key, to_give.key))
# Call the object script's at_give() method. # Call the object script's at_give() method.
to_give.at_give(caller, target) to_give.at_give(caller, target)

View file

@ -202,12 +202,6 @@ class MuxCommand(Command):
else: else:
self.character = None self.character = None
def get_command_info(self):
"""
Update of parent class's get_command_info() for MuxCommand.
"""
self.get_command_info()
def get_command_info(self): def get_command_info(self):
""" """
Update of parent class's get_command_info() for MuxCommand. Update of parent class's get_command_info() for MuxCommand.

View file

@ -448,7 +448,7 @@ def format_script_list(scripts):
table.add_row( table.add_row(
script.id, script.id,
script.obj.key if (hasattr(script, "obj") and script.obj) else "<Global>", f"{script.obj.key}({script.obj.dbref})" if (hasattr(script, "obj") and script.obj) else "<Global>",
script.key, script.key,
script.interval if script.interval > 0 else "--", script.interval if script.interval > 0 else "--",
nextrep, nextrep,

View file

@ -51,6 +51,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
self.attributes.add("keep_log", cdict["keep_log"]) self.attributes.add("keep_log", cdict["keep_log"])
if cdict.get("desc"): if cdict.get("desc"):
self.attributes.add("desc", cdict["desc"]) self.attributes.add("desc", cdict["desc"])
if cdict.get("tags"):
self.tags.batch_add(*cdict["tags"])
def basetype_setup(self): def basetype_setup(self):
# delayed import of the channelhandler # delayed import of the channelhandler
@ -394,7 +396,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
to build senders for the message. to build senders for the message.
sender_strings (list, optional): Name strings of senders. Used for external sender_strings (list, optional): Name strings of senders. Used for external
connections where the sender is not an account or object. connections where the sender is not an account or object.
When this is defined, external will be assumed. When this is defined, external will be assumed. The list will be
filtered so each sender-string only occurs once.
keep_log (bool or None, optional): This allows to temporarily change the logging status of keep_log (bool or None, optional): This allows to temporarily change the logging status of
this channel message. If `None`, the Channel's `keep_log` Attribute will this channel message. If `None`, the Channel's `keep_log` Attribute will
be used. If `True` or `False`, that logging status will be used for this be used. If `True` or `False`, that logging status will be used for this
@ -425,6 +428,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
msgobj = self.pre_send_message(msgobj) msgobj = self.pre_send_message(msgobj)
if not msgobj: if not msgobj:
return False return False
if sender_strings:
sender_strings = list(set(make_iter(sender_strings)))
msgobj = self.message_transform( msgobj = self.message_transform(
msgobj, emit=emit, sender_strings=sender_strings, external=external msgobj, emit=emit, sender_strings=sender_strings, external=external
) )

View file

@ -209,7 +209,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
# lockstring of newly created objects, for easy overloading. # lockstring of newly created objects, for easy overloading.
# Will be formatted with the appropriate attributes. # Will be formatted with the appropriate attributes.
lockstring = "control:id({account_id}) or perm(Admin);" "delete:id({account_id}) or perm(Admin)" lockstring = "control:id({account_id}) or perm(Admin);delete:id({account_id}) or perm(Admin)"
objects = ObjectManager() objects = ObjectManager()
@ -2042,10 +2042,11 @@ class DefaultCharacter(DefaultObject):
_content_types = ("character",) _content_types = ("character",)
# lockstring of newly created rooms, for easy overloading. # lockstring of newly created rooms, for easy overloading.
# Will be formatted with the appropriate attributes. # Will be formatted with the appropriate attributes.
lockstring = "puppet:id({character_id}) or pid({account_id}) or perm(Developer) or pperm(Developer);delete:id({account_id}) or perm(Admin)" lockstring = ("puppet:id({character_id}) or pid({account_id}) or perm(Developer) or pperm(Developer);"
"delete:id({account_id}) or perm(Admin)")
@classmethod @classmethod
def create(cls, key, account, **kwargs): def create(cls, key, account=None, **kwargs):
""" """
Creates a basic Character with default parameters, unless otherwise Creates a basic Character with default parameters, unless otherwise
specified or extended. specified or extended.
@ -2054,8 +2055,8 @@ class DefaultCharacter(DefaultObject):
Args: Args:
key (str): Name of the new Character. key (str): Name of the new Character.
account (obj): Account to associate this Character with. Required as account (obj, optional): Account to associate this Character with.
an argument, but one can fake it out by supplying None-- it will If unset supplying None-- it will
change the default lockset and skip creator attribution. change the default lockset and skip creator attribution.
Keyword args: Keyword args:
@ -2305,7 +2306,7 @@ class DefaultRoom(DefaultObject):
) )
@classmethod @classmethod
def create(cls, key, account, **kwargs): def create(cls, key, account=None, **kwargs):
""" """
Creates a basic Room with default parameters, unless otherwise Creates a basic Room with default parameters, unless otherwise
specified or extended. specified or extended.
@ -2314,7 +2315,9 @@ class DefaultRoom(DefaultObject):
Args: Args:
key (str): Name of the new Room. key (str): Name of the new Room.
account (obj): Account to associate this Room with. account (obj, optional): Account to associate this Room with. If
given, it will be given specific control/edit permissions to this
object (along with normal Admin perms). If not given, default
Keyword args: Keyword args:
description (str): Brief description for this object. description (str): Brief description for this object.
@ -2343,13 +2346,20 @@ class DefaultRoom(DefaultObject):
# Get description, if provided # Get description, if provided
description = kwargs.pop("description", "") description = kwargs.pop("description", "")
# get locks if provided
locks = kwargs.pop("locks", "")
try: try:
# Create the Room # Create the Room
obj = create.create_object(**kwargs) obj = create.create_object(**kwargs)
# Set appropriate locks # Add locks
lockstring = kwargs.get("locks", cls.lockstring.format(id=account.id)) if not locks and account:
obj.locks.add(lockstring) locks = cls.lockstring.format(**{"id": account.id})
elif not locks and not account:
locks = cls.lockstring(**{"id": obj.id})
obj.locks.add(locks)
# Record creator id and creation IP # Record creator id and creation IP
if ip: if ip:
@ -2499,7 +2509,7 @@ class DefaultExit(DefaultObject):
# Command hooks # Command hooks
@classmethod @classmethod
def create(cls, key, account, source, dest, **kwargs): def create(cls, key, source, dest, account=None, **kwargs):
""" """
Creates a basic Exit with default parameters, unless otherwise Creates a basic Exit with default parameters, unless otherwise
specified or extended. specified or extended.
@ -2543,13 +2553,18 @@ class DefaultExit(DefaultObject):
description = kwargs.pop("description", "") description = kwargs.pop("description", "")
locks = kwargs.get("locks", "")
try: try:
# Create the Exit # Create the Exit
obj = create.create_object(**kwargs) obj = create.create_object(**kwargs)
# Set appropriate locks # Set appropriate locks
lockstring = kwargs.get("locks", cls.lockstring.format(id=account.id)) if not locks and account:
obj.locks.add(lockstring) locks = cls.lockstring.format(**{"id": account.id})
elif not locks and not account:
locks = cls.lockstring.format(**{"id": obj.id})
obj.locks.add(locks)
# Record creator id and creation IP # Record creator id and creation IP
if ip: if ip:

View file

@ -59,7 +59,7 @@ class DefaultObjectTest(EvenniaTest):
def test_exit_create(self): def test_exit_create(self):
description = "The steaming depths of the dumpster, ripe with refuse in various states of decomposition." description = "The steaming depths of the dumpster, ripe with refuse in various states of decomposition."
obj, errors = DefaultExit.create( obj, errors = DefaultExit.create(
"in", self.account, self.room1, self.room2, description=description, ip=self.ip "in", self.room1, self.room2, account=self.account, description=description, ip=self.ip
) )
self.assertTrue(obj, errors) self.assertTrue(obj, errors)
self.assertFalse(errors, errors) self.assertFalse(errors, errors)

View file

@ -1094,13 +1094,13 @@ class AttributeHandler:
repeat-calling add when having many Attributes to add. repeat-calling add when having many Attributes to add.
Args: Args:
*args (tuple): Tuples of varying length representing the *args (tuple): Each argument should be a tuples (can be of varying
Attribute to add to this object. Supported tuples are length) representing the Attribute to add to this object.
Supported tuples are
- (key, value) - `(key, value)`
- (key, value, category) - `(key, value, category)`
- (key, value, category, lockstring) - `(key, value, category, lockstring)`
- (key, value, category, lockstring, default_access) - `(key, value, category, lockstring, default_access)`
Keyword args: Keyword args:
strattr (bool): If `True`, value must be a string. This strattr (bool): If `True`, value must be a string. This

View file

@ -36,7 +36,7 @@ class Tag(models.Model):
indexed for efficient lookup in the database. Tags are shared indexed for efficient lookup in the database. Tags are shared
between objects - a new tag is only created if the key+category between objects - a new tag is only created if the key+category
combination did not previously exist, making them unsuitable for combination did not previously exist, making them unsuitable for
storing object-related data (for this a full tag should be storing object-related data (for this a regular Attribute should be
used). used).
The 'db_data' field is intended as a documentation field for the The 'db_data' field is intended as a documentation field for the
@ -449,8 +449,8 @@ class TagHandler(object):
Batch-add tags from a list of tuples. Batch-add tags from a list of tuples.
Args: Args:
tuples (tuple or str): Any number of `tagstr` keys, `(keystr, category)` or *args (tuple or str): Each argument should be a `tagstr` keys or tuple `(keystr, category)` or
`(keystr, category, data)` tuples. `(keystr, category, data)`. It's possible to mix input types.
Notes: Notes:
This will generate a mimimal number of self.add calls, This will generate a mimimal number of self.add calls,

View file

@ -42,6 +42,17 @@ class TestAttributes(EvenniaTest):
self.obj1.attributes.add(key, value) self.obj1.attributes.add(key, value)
self.assertEqual(self.obj1.attributes.get(key), value) self.assertEqual(self.obj1.attributes.get(key), value)
def test_batch_add(self):
attrs = [("key1", "value1"),
("key2", "value2", "category2"),
("key3", "value3"),
("key4", "value4", "category4", "attrread:id(1)", False)]
new_attrs = self.obj1.attributes.batch_add(*attrs)
attrobj = self.obj1.attributes.get(key="key4", category="category4", return_obj=True)
self.assertEqual(attrobj.value, "value4")
self.assertEqual(attrobj.category, "category4")
self.assertEqual(attrobj.locks.all(), ["attrread:id(1)"])
class TestTypedObjectManager(EvenniaTest): class TestTypedObjectManager(EvenniaTest):
def _manager(self, methodname, *args, **kwargs): def _manager(self, methodname, *args, **kwargs):
@ -123,3 +134,16 @@ class TestTypedObjectManager(EvenniaTest):
self._manager("get_by_tag", ["tagA", "tagB"], ["categoryA", "categoryB"], match="any"), self._manager("get_by_tag", ["tagA", "tagB"], ["categoryA", "categoryB"], match="any"),
[], [],
) )
def test_batch_add(self):
tags = ["tag1",
("tag2", "category2"),
"tag3",
("tag4", "category4", "data4")
]
self.obj1.tags.batch_add(*tags)
self.assertEqual(self.obj1.tags.get("tag1"), "tag1")
tagobj = self.obj1.tags.get("tag4", category="category4", return_tagobj=True)
self.assertEqual(tagobj.db_key, "tag4")
self.assertEqual(tagobj.db_category, "category4")
self.assertEqual(tagobj.db_data, "data4")

View file

@ -304,7 +304,7 @@ script = create_script
# #
def create_help_entry(key, entrytext, category="General", locks=None, aliases=None): 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 Create a static help entry in the help database. Note that Command
help entries are dynamic and directly taken from the __doc__ help entries are dynamic and directly taken from the __doc__
@ -317,7 +317,8 @@ def create_help_entry(key, entrytext, category="General", locks=None, aliases=No
entrytext (str): The body of te help entry entrytext (str): The body of te help entry
category (str, optional): The help category of the entry. category (str, optional): The help category of the entry.
locks (str, optional): A lockstring to restrict access. locks (str, optional): A lockstring to restrict access.
aliases (list of str): List of alternative (likely shorter) keynames. aliases (list of str, optional): List of alternative (likely shorter) keynames.
tags (lst, optional): List of tags or tuples `(tag, category)`.
Returns: Returns:
help (HelpEntry): A newly created help entry. help (HelpEntry): A newly created help entry.
@ -335,7 +336,9 @@ def create_help_entry(key, entrytext, category="General", locks=None, aliases=No
if locks: if locks:
new_help.locks.add(locks) new_help.locks.add(locks)
if aliases: if aliases:
new_help.aliases.add(aliases) new_help.aliases.add(make_iter(aliases))
if tags:
new_help.tags.batch_add(*tags)
new_help.save() new_help.save()
return new_help return new_help
except IntegrityError: except IntegrityError:
@ -357,7 +360,8 @@ help_entry = create_help_entry
# Comm system methods # Comm system methods
def create_message(senderobj, message, channels=None, receivers=None, locks=None, header=None): def create_message(senderobj, message, channels=None, receivers=None,
locks=None, tags=None, header=None):
""" """
Create a new communication Msg. Msgs represent a unit of Create a new communication Msg. Msgs represent a unit of
database-persistent communication between entites. database-persistent communication between entites.
@ -373,6 +377,7 @@ def create_message(senderobj, message, channels=None, receivers=None, locks=None
receivers (Object, Account, str or list): An Account/Object to send receivers (Object, Account, str or list): An Account/Object to send
to, or a list of them. May be Account objects or accountnames. to, or a list of them. May be Account objects or accountnames.
locks (str): Lock definition string. 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 header (str): Mime-type or other optional information for the message
Notes: Notes:
@ -399,6 +404,9 @@ def create_message(senderobj, message, channels=None, receivers=None, locks=None
new_message.receivers = receiver new_message.receivers = receiver
if locks: if locks:
new_message.locks.add(locks) new_message.locks.add(locks)
if tags:
new_message.tags.batch_add(*tags)
new_message.save() new_message.save()
return new_message return new_message
@ -407,7 +415,7 @@ message = create_message
create_msg = create_message create_msg = create_message
def create_channel(key, aliases=None, desc=None, locks=None, keep_log=True, typeclass=None): 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 Create A communication Channel. A Channel serves as a central hub
for distributing Msgs to groups of people without specifying the for distributing Msgs to groups of people without specifying the
@ -426,6 +434,7 @@ def create_channel(key, aliases=None, desc=None, locks=None, keep_log=True, type
keep_log (bool): Log channel throughput. keep_log (bool): Log channel throughput.
typeclass (str or class): The typeclass of the Channel (not typeclass (str or class): The typeclass of the Channel (not
often used). often used).
tags (list): A list of tags or tuples `(tag, category)`.
Returns: Returns:
channel (Channel): A newly created channel. channel (Channel): A newly created channel.
@ -442,7 +451,7 @@ def create_channel(key, aliases=None, desc=None, locks=None, keep_log=True, type
# store call signature for the signal # store call signature for the signal
new_channel._createdict = dict( new_channel._createdict = dict(
key=key, aliases=aliases, desc=desc, locks=locks, keep_log=keep_log 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 # this will trigger the save signal which in turn calls the

View file

@ -3,6 +3,7 @@ Tests of create functions
""" """
from django.test import TestCase
from evennia.utils.test_resources import EvenniaTest from evennia.utils.test_resources import EvenniaTest
from evennia.scripts.scripts import DefaultScript from evennia.scripts.scripts import DefaultScript
from evennia.utils import create from evennia.utils import create
@ -76,3 +77,93 @@ class TestCreateScript(EvenniaTest):
assert script.repeats == 1 assert script.repeats == 1
assert script.key == "test_script" assert script.key == "test_script"
script.stop() script.stop()
class TestCreateHelpEntry(TestCase):
help_entry = """
Qui laborum voluptas quis commodi ipsum quo temporibus eum. Facilis
assumenda facilis architecto in corrupti. Est placeat eum amet qui beatae
reiciendis. Accusamus vel aspernatur ab ex. Quam expedita sed expedita
consequuntur est dolorum non exercitationem.
Ipsa vel ut dolorem voluptatem adipisci velit. Sit odit temporibus mollitia
illum ipsam placeat. Rem et ipsum dolor. Hic eum tempore excepturi qui veniam
magni.
Excepturi quam repellendus inventore excepturi fugiat quo quasi molestias.
Nostrum ut assumenda enim a. Repellat quis omnis est officia accusantium. Fugit
facere qui aperiam. Perspiciatis commodi dolores ipsam nemo consequatur
quisquam qui non. Adipisci et molestias voluptatum est sed fugiat facere.
"""
def test_create_help_entry__simple(self):
entry = create.create_help_entry("testentry", self.help_entry, category="Testing")
self.assertEqual(entry.key, "testentry")
self.assertEqual(entry.entrytext, self.help_entry)
self.assertEqual(entry.help_category, "Testing")
# creating same-named entry should not work (must edit existing)
self.assertFalse(create.create_help_entry("testentry", "testtext"))
def test_create_help_entry__complex(self):
locks = "foo:false();bar:true()"
aliases = ['foo', 'bar', 'tst']
tags = [("tag1", "help"), ("tag2", "help"), ("tag3", "help")]
entry = create.create_help_entry("testentry", self.help_entry, category="Testing",
locks=locks, aliases=aliases, tags=tags)
self.assertTrue(all(lock in entry.locks.all() for lock in locks.split(";")))
self.assertEqual(list(entry.aliases.all()).sort(), aliases.sort())
self.assertEqual(entry.tags.all(return_key_and_category=True), tags)
class TestCreateMessage(EvenniaTest):
msgtext = """
Qui laborum voluptas quis commodi ipsum quo temporibus eum. Facilis
assumenda facilis architecto in corrupti. Est placeat eum amet qui beatae
reiciendis. Accusamus vel aspernatur ab ex. Quam expedita sed expedita
consequuntur est dolorum non exercitationem.
"""
def test_create_msg__simple(self):
msg = create.create_message(self.char1, self.msgtext, header="TestHeader")
self.assertEqual(msg.message, self.msgtext)
self.assertEqual(msg.header, "TestHeader")
self.assertEqual(msg.senders, [self.char1])
def test_create_msg__channel(self):
chan1 = create.create_channel("DummyChannel1")
chan2 = create.create_channel("DummyChannel2")
msg = create.create_message(self.char1, self.msgtext, channels=[chan1, chan2], header="TestHeader")
self.assertEqual(list(msg.channels), [chan1, chan2])
def test_create_msg__custom(self):
locks = "foo:false();bar:true()"
tags = ["tag1", "tag2", "tag3"]
msg = create.create_message(self.char1, self.msgtext, header="TestHeader",
receivers=[self.char1, self.char2], locks=locks, tags=tags)
self.assertEqual(msg.receivers, [self.char1, self.char2])
self.assertTrue(all(lock in msg.locks.all() for lock in locks.split(";")))
self.assertEqual(msg.tags.all(), tags)
class TestCreateChannel(TestCase):
def test_create_channel__simple(self):
chan = create.create_channel("TestChannel1", desc="Testing channel")
self.assertEqual(chan.key, "TestChannel1")
self.assertEqual(chan.db.desc, "Testing channel")
def test_create_channel__complex(self):
locks = "foo:false();bar:true()"
tags = ["tag1", "tag2", "tag3"]
aliases = ['foo', 'bar', 'tst']
chan = create.create_channel("TestChannel2", desc="Testing channel",
aliases=aliases, locks=locks, tags=tags)
self.assertTrue(all(lock in chan.locks.all() for lock in locks.split(";")))
self.assertEqual(chan.tags.all(), tags)
self.assertEqual(list(chan.aliases.all()).sort(), aliases.sort())