Resolve merge conflicts

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

View file

@ -53,10 +53,10 @@ without arguments starts a full interactive Python console.
- EvMore auto-justify now defaults to False since this works better with all types
of texts (such as tables). New `justify` bool. Old `justify_kwargs` remains
but is now only used to pass extra kwargs into the justify function.
- EvMore `text` argument can now also be a list or a queryset. Querysets will be
sliced to only return the required data per page. EvMore takes a new kwarg
`page_formatter` which will be called for each page. This allows to customize
the display of queryset data, build a new EvTable per page etc.
- EvMore `text` argument can now also be a list or a queryset. Querysets will be
sliced to only return the required data per page. EvMore takes a new kwarg
`page_formatter` which will be called for each page. This allows to customize
the display of queryset data, build a new EvTable per page etc.
- Improve performance of `find` and `objects` commands on large data sets (strikaco)
- New `CHANNEL_HANDLER_CLASS` setting allows for replacing the ChannelHandler entirely.
- Made `py` interactive mode support regular quit() and more verbose.
@ -66,20 +66,23 @@ without arguments starts a full interactive Python console.
`.get_command_info()` method for easier overloading and access. (Volund)
- Removed unused `CYCLE_LOGFILES` setting. Added `SERVER_LOG_DAY_ROTATION`
and `SERVER_LOG_MAX_SIZE` (and equivalent for PORTAL) to control log rotation.
- Addded `inside_rec` lockfunc - if room is locked, the normal `inside()` lockfunc will
- Addded `inside_rec` lockfunc - if room is locked, the normal `inside()` lockfunc will
fail e.g. for your inventory objs (since their loc is you), whereas this will pass.
- RPSystem contrib's CmdRecog will now list all recogs if no arg is given. Also multiple
bugfixes.
- Remove `dummy@example.com` as a default account email when unset, a string is no longer
- Remove `dummy@example.com` as a default account email when unset, a string is no longer
required by Django.
- Fixes to `spawn`, make updating an existing prototype/object work better. Add `/raw` switch
to `spawn` command to extract the raw prototype dict for manual editing.
- `list_to_string` is now `iter_to_string` (but old name still works as legacy alias). It will
now accept any input, including generators and single values.
- `list_to_string` is now `iter_to_string` (but old name still works as legacy alias). It will
now accept any input, including generators and single values.
- EvTable should now correctly handle columns with wider asian-characters in them.
- 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 `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)

View file

@ -154,7 +154,8 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
if not account.is_superuser and (
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
from evennia.objects.models import ObjectDB

View file

@ -426,11 +426,14 @@ class CmdGet(COMMAND_DEFAULT_CLASS):
if not obj.at_before_get(caller):
return
obj.move_to(caller, quiet=True)
caller.msg("You pick up %s." % obj.name)
caller.location.msg_contents("%s picks up %s." % (caller.name, obj.name), exclude=caller)
# calling at_get hook method
obj.at_get(caller)
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.location.msg_contents("%s picks up %s." % (caller.name, obj.name), exclude=caller)
# calling at_get hook method
obj.at_get(caller)
class CmdDrop(COMMAND_DEFAULT_CLASS):
@ -471,11 +474,14 @@ class CmdDrop(COMMAND_DEFAULT_CLASS):
if not obj.at_before_drop(caller):
return
obj.move_to(caller.location, quiet=True)
caller.msg("You drop %s." % (obj.name,))
caller.location.msg_contents("%s drops %s." % (caller.name, obj.name), exclude=caller)
# Call the object script's at_drop() method.
obj.at_drop(caller)
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.location.msg_contents("%s drops %s." % (caller.name, obj.name), exclude=caller)
# Call the object script's at_drop() method.
obj.at_drop(caller)
class CmdGive(COMMAND_DEFAULT_CLASS):
@ -522,11 +528,14 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
return
# give object
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))
# Call the object script's at_give() method.
to_give.at_give(caller, target)
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))
target.msg("%s gives you %s." % (caller.key, to_give.key))
# Call the object script's at_give() method.
to_give.at_give(caller, target)
class CmdSetDesc(COMMAND_DEFAULT_CLASS):

View file

@ -202,12 +202,6 @@ class MuxCommand(Command):
else:
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):
"""
Update of parent class's get_command_info() for MuxCommand.

View file

@ -448,7 +448,7 @@ def format_script_list(scripts):
table.add_row(
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.interval if script.interval > 0 else "--",
nextrep,

View file

@ -1136,7 +1136,7 @@ class TestBuilding(CommandTest):
"= Obj",
"To create a global script you need scripts/add <typeclass>.",
)
self.call(building.CmdScript(), "Obj = ", "dbref obj")
self.call(building.CmdScript(), "Obj = ", "dbref obj")
self.call(
building.CmdScript(), "/start Obj", "0 scripts started on Obj"

View file

@ -51,6 +51,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
self.attributes.add("keep_log", cdict["keep_log"])
if cdict.get("desc"):
self.attributes.add("desc", cdict["desc"])
if cdict.get("tags"):
self.tags.batch_add(*cdict["tags"])
def basetype_setup(self):
# delayed import of the channelhandler
@ -394,7 +396,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
to build senders for the message.
sender_strings (list, optional): Name strings of senders. Used for external
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
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
@ -425,6 +428,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
msgobj = self.pre_send_message(msgobj)
if not msgobj:
return False
if sender_strings:
sender_strings = list(set(make_iter(sender_strings)))
msgobj = self.message_transform(
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.
# 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()
@ -2042,10 +2042,11 @@ class DefaultCharacter(DefaultObject):
_content_types = ("character",)
# lockstring of newly created rooms, for easy overloading.
# 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
def create(cls, key, account, **kwargs):
def create(cls, key, account=None, **kwargs):
"""
Creates a basic Character with default parameters, unless otherwise
specified or extended.
@ -2054,8 +2055,8 @@ class DefaultCharacter(DefaultObject):
Args:
key (str): Name of the new Character.
account (obj): Account to associate this Character with. Required as
an argument, but one can fake it out by supplying None-- it will
account (obj, optional): Account to associate this Character with.
If unset supplying None-- it will
change the default lockset and skip creator attribution.
Keyword args:
@ -2305,7 +2306,7 @@ class DefaultRoom(DefaultObject):
)
@classmethod
def create(cls, key, account, **kwargs):
def create(cls, key, account=None, **kwargs):
"""
Creates a basic Room with default parameters, unless otherwise
specified or extended.
@ -2314,7 +2315,9 @@ class DefaultRoom(DefaultObject):
Args:
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:
description (str): Brief description for this object.
@ -2343,13 +2346,20 @@ class DefaultRoom(DefaultObject):
# Get description, if provided
description = kwargs.pop("description", "")
# get locks if provided
locks = kwargs.pop("locks", "")
try:
# Create the Room
obj = create.create_object(**kwargs)
# Set appropriate locks
lockstring = kwargs.get("locks", cls.lockstring.format(id=account.id))
obj.locks.add(lockstring)
# Add locks
if not locks and account:
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
if ip:
@ -2499,7 +2509,7 @@ class DefaultExit(DefaultObject):
# Command hooks
@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
specified or extended.
@ -2543,13 +2553,18 @@ class DefaultExit(DefaultObject):
description = kwargs.pop("description", "")
locks = kwargs.get("locks", "")
try:
# Create the Exit
obj = create.create_object(**kwargs)
# Set appropriate locks
lockstring = kwargs.get("locks", cls.lockstring.format(id=account.id))
obj.locks.add(lockstring)
if not locks and account:
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
if ip:

View file

@ -59,7 +59,7 @@ class DefaultObjectTest(EvenniaTest):
def test_exit_create(self):
description = "The steaming depths of the dumpster, ripe with refuse in various states of decomposition."
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.assertFalse(errors, errors)

View file

@ -1094,13 +1094,13 @@ class AttributeHandler:
repeat-calling add when having many Attributes to add.
Args:
*args (tuple): Tuples of varying length representing the
Attribute to add to this object. Supported tuples are
- (key, value)
- (key, value, category)
- (key, value, category, lockstring)
- (key, value, category, lockstring, default_access)
*args (tuple): Each argument should be a tuples (can be of varying
length) representing the Attribute to add to this object.
Supported tuples are
- `(key, value)`
- `(key, value, category)`
- `(key, value, category, lockstring)`
- `(key, value, category, lockstring, default_access)`
Keyword args:
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
between objects - a new tag is only created if the key+category
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).
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.
Args:
tuples (tuple or str): Any number of `tagstr` keys, `(keystr, category)` or
`(keystr, category, data)` tuples.
*args (tuple or str): Each argument should be a `tagstr` keys or tuple `(keystr, category)` or
`(keystr, category, data)`. It's possible to mix input types.
Notes:
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.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):
def _manager(self, methodname, *args, **kwargs):
@ -123,3 +134,16 @@ class TestTypedObjectManager(EvenniaTest):
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
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
category (str, optional): The help category of the entry.
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:
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:
new_help.locks.add(locks)
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()
return new_help
except IntegrityError:
@ -357,7 +360,8 @@ help_entry = create_help_entry
# 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
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
to, or a list of them. May be Account objects or accountnames.
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:
@ -399,6 +404,9 @@ def create_message(senderobj, message, channels=None, receivers=None, locks=None
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
@ -407,7 +415,7 @@ message = 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
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.
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.
@ -442,7 +451,7 @@ def create_channel(key, aliases=None, desc=None, locks=None, keep_log=True, type
# store call signature for the signal
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

View file

@ -3,6 +3,7 @@ Tests of create functions
"""
from django.test import TestCase
from evennia.utils.test_resources import EvenniaTest
from evennia.scripts.scripts import DefaultScript
from evennia.utils import create
@ -76,3 +77,93 @@ class TestCreateScript(EvenniaTest):
assert script.repeats == 1
assert script.key == "test_script"
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())