diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index f8d0246f39..f3e58790f5 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -594,6 +594,9 @@ class CmdDesc(COMMAND_DEFAULT_CLASS): if not obj: return + if not (obj.access(self.caller, 'control') or obj.access(self.caller, 'edit')): + self.caller.msg("You don't have permission to edit the description of %s." % obj.key) + self.caller.db.evmenu_target = obj # launch the editor EvEditor(self.caller, loadfunc=_desc_load, savefunc=_desc_save, @@ -623,7 +626,7 @@ class CmdDesc(COMMAND_DEFAULT_CLASS): if not obj: return desc = self.args - if obj.access(caller, "edit"): + if (obj.access(self.caller, 'control') or obj.access(self.caller, 'edit')): obj.db.desc = desc caller.msg("The description was set on %s." % obj.get_display_name(caller)) else: @@ -1646,6 +1649,10 @@ class CmdSetAttribute(ObjManipCommand): result = [] if "edit" in self.switches: # edit in the line editor + if not (obj.access(self.caller, 'control') or obj.access(self.caller, 'edit')): + caller.msg("You don't have permission to edit %s." % obj.key) + return + if len(attrs) > 1: caller.msg("The Line editor can only be applied " "to one attribute at a time.") @@ -1666,12 +1673,18 @@ class CmdSetAttribute(ObjManipCommand): return else: # deleting the attribute(s) + if not (obj.access(self.caller, 'control') or obj.access(self.caller, 'edit')): + caller.msg("You don't have permission to edit %s." % obj.key) + return for attr in attrs: if not self.check_attr(obj, attr): continue result.append(self.rm_attr(obj, attr)) else: # setting attribute(s). Make sure to convert to real Python type before saving. + if not (obj.access(self.caller, 'control') or obj.access(self.caller, 'edit')): + caller.msg("You don't have permission to edit %s." % obj.key) + return for attr in attrs: if not self.check_attr(obj, attr): continue @@ -1905,9 +1918,16 @@ class CmdLock(ObjManipCommand): obj = caller.search(objname) if not obj: return - if not (obj.access(caller, 'control') or obj.access(caller, "edit")): + has_control_access = obj.access(caller, 'control') + if access_type == 'control' and not has_control_access: + # only allow to change 'control' access if you have 'control' access already + caller.msg("You need 'control' access to change this type of lock.") + return + + if not has_control_access or obj.access(caller, "edit"): caller.msg("You are not allowed to do that.") return + lockdef = obj.locks.get(access_type) if lockdef: @@ -2386,7 +2406,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS): Examples: @tel Limbo - @tel/quiet box Limbo + @tel/quiet box = Limbo @tel/tonone box Switches: @@ -2402,7 +2422,8 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS): loc - teleport object to the target's location instead of its contents Teleports an object somewhere. If no object is given, you yourself - is teleported to the target location. """ + is teleported to the target location. + """ key = "@tel" aliases = "@teleport" options = ("quiet", "intoexit", "tonone", "loc") diff --git a/evennia/commands/default/cmdset_account.py b/evennia/commands/default/cmdset_account.py index 4e357a2ce0..d7b887c017 100644 --- a/evennia/commands/default/cmdset_account.py +++ b/evennia/commands/default/cmdset_account.py @@ -11,7 +11,7 @@ command method rather than caller.msg(). from evennia.commands.cmdset import CmdSet from evennia.commands.default import help, comms, admin, system -from evennia.commands.default import building, account +from evennia.commands.default import building, account, general class AccountCmdSet(CmdSet): @@ -39,6 +39,9 @@ class AccountCmdSet(CmdSet): self.add(account.CmdColorTest()) self.add(account.CmdQuell()) + # nicks + self.add(general.CmdNick()) + # testing self.add(building.CmdExamine()) diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 932c4fe898..254081ba18 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -143,14 +143,14 @@ class CmdNick(COMMAND_DEFAULT_CLASS): return re.sub(r"(\$[0-9]+|\*|\?|\[.+?\])", r"|Y\1|n", string) caller = self.caller - account = self.caller.account or caller switches = self.switches - nicktypes = [switch for switch in switches if switch in ( - "object", "account", "inputline")] or ["inputline"] + nicktypes = [switch for switch in switches if switch in ("object", "account", "inputline")] + specified_nicktype = bool(nicktypes) + nicktypes = nicktypes if specified_nicktype else ["inputline"] nicklist = (utils.make_iter(caller.nicks.get(category="inputline", return_obj=True) or []) + utils.make_iter(caller.nicks.get(category="object", return_obj=True) or []) + - utils.make_iter(account.nicks.get(category="account", return_obj=True) or [])) + utils.make_iter(caller.nicks.get(category="account", return_obj=True) or [])) if 'list' in switches or self.cmdstring in ("nicks", "@nicks"): @@ -173,29 +173,99 @@ class CmdNick(COMMAND_DEFAULT_CLASS): if 'delete' in switches or 'del' in switches: if not self.args or not self.lhs: - caller.msg("usage nick/delete #num ('nicks' for list)") + caller.msg("usage nick/delete or <#num> ('nicks' for list)") return # see if a number was given arg = self.args.lstrip("#") + oldnicks = [] if arg.isdigit(): # we are given a index in nicklist delindex = int(arg) if 0 < delindex <= len(nicklist): - oldnick = nicklist[delindex - 1] - _, _, old_nickstring, old_replstring = oldnick.value + oldnicks.append(nicklist[delindex - 1]) else: caller.msg("Not a valid nick index. See 'nicks' for a list.") return - nicktype = oldnick.category - nicktypestr = "%s-nick" % nicktype.capitalize() + else: + if not specified_nicktype: + nicktypes = ("object", "account", "inputline") + for nicktype in nicktypes: + oldnicks.append(caller.nicks.get(arg, category=nicktype, return_obj=True)) - if nicktype == "account": - account.nicks.remove(old_nickstring, category=nicktype) - else: + oldnicks = [oldnick for oldnick in oldnicks if oldnick] + if oldnicks: + for oldnick in oldnicks: + nicktype = oldnick.category + nicktypestr = "%s-nick" % nicktype.capitalize() + _, _, old_nickstring, old_replstring = oldnick.value caller.nicks.remove(old_nickstring, category=nicktype) - caller.msg("%s removed: '|w%s|n' -> |w%s|n." % ( - nicktypestr, old_nickstring, old_replstring)) - return + caller.msg("%s removed: '|w%s|n' -> |w%s|n." % ( + nicktypestr, old_nickstring, old_replstring)) + else: + caller.msg("No matching nicks to remove.") + return + + if not self.rhs and self.lhs: + # check what a nick is set to + strings = [] + if not specified_nicktype: + nicktypes = ("object", "account", "inputline") + for nicktype in nicktypes: + nicks = utils.make_iter(caller.nicks.get(category=nicktype, return_obj=True)) + for nick in nicks: + _, _, nick, repl = nick.value + if nick.startswith(self.lhs): + strings.append("{}-nick: '{}' -> '{}'".format( + nicktype.capitalize(), nick, repl)) + if strings: + caller.msg("\n".join(strings)) + else: + caller.msg("No nicks found matching '{}'".format(self.lhs)) + return + + if not self.rhs and self.lhs: + # check what a nick is set to + strings = [] + if not specified_nicktype: + nicktypes = ("object", "account", "inputline") + for nicktype in nicktypes: + if nicktype == "account": + obj = account + else: + obj = caller + nicks = utils.make_iter(obj.nicks.get(category=nicktype, return_obj=True)) + for nick in nicks: + _, _, nick, repl = nick.value + if nick.startswith(self.lhs): + strings.append("{}-nick: '{}' -> '{}'".format( + nicktype.capitalize(), nick, repl)) + if strings: + caller.msg("\n".join(strings)) + else: + caller.msg("No nicks found matching '{}'".format(self.lhs)) + return + + if not self.rhs and self.lhs: + # check what a nick is set to + strings = [] + if not specified_nicktype: + nicktypes = ("object", "account", "inputline") + for nicktype in nicktypes: + if nicktype == "account": + obj = account + else: + obj = caller + nicks = utils.make_iter(obj.nicks.get(category=nicktype, return_obj=True)) + for nick in nicks: + _, _, nick, repl = nick.value + if nick.startswith(self.lhs): + strings.append("{}-nick: '{}' -> '{}'".format( + nicktype.capitalize(), nick, repl)) + if strings: + caller.msg("\n".join(strings)) + else: + caller.msg("No nicks found matching '{}'".format(self.lhs)) + return if not self.args or not self.lhs: caller.msg("Usage: nick[/switches] nickname = [realname]") @@ -214,16 +284,11 @@ class CmdNick(COMMAND_DEFAULT_CLASS): errstring = "" string = "" for nicktype in nicktypes: - if nicktype == "account": - obj = account - else: - obj = caller - nicktypestr = "%s-nick" % nicktype.capitalize() old_nickstring = None old_replstring = None - oldnick = obj.nicks.get(key=nickstring, category=nicktype, return_obj=True) + oldnick = caller.nicks.get(key=nickstring, category=nicktype, return_obj=True) if oldnick: _, _, old_nickstring, old_replstring = oldnick.value if replstring: @@ -238,7 +303,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS): else: string += "\n%s '|w%s|n' mapped to '|w%s|n'." % (nicktypestr, nickstring, replstring) try: - obj.nicks.add(nickstring, replstring, category=nicktype) + caller.nicks.add(nickstring, replstring, category=nicktype) except NickTemplateInvalid: caller.msg("You must use the same $-markers both in the nick and in the replacement.") return diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index f581c32589..e79277de9b 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -132,8 +132,8 @@ class TestGeneral(CommandTest): self.call(general.CmdNick(), "/object testalias = testaliasedstring3", "Objectnick 'testalias' mapped to 'testaliasedstring3'.") self.assertEqual(u"testaliasedstring1", self.char1.nicks.get("testalias")) - self.assertEqual(None, self.char1.nicks.get("testalias", category="account")) - self.assertEqual(u"testaliasedstring2", self.char1.account.nicks.get("testalias", category="account")) + self.assertEqual(u"testaliasedstring2", self.char1.nicks.get("testalias", category="account")) + self.assertEqual(None, self.char1.account.nicks.get("testalias", category="account")) self.assertEqual(u"testaliasedstring3", self.char1.nicks.get("testalias", category="object")) def test_get_and_drop(self): diff --git a/evennia/comms/channelhandler.py b/evennia/comms/channelhandler.py index 02c9e19291..058b7d9ba4 100644 --- a/evennia/comms/channelhandler.py +++ b/evennia/comms/channelhandler.py @@ -159,7 +159,7 @@ class ChannelHandler(object): """ The ChannelHandler manages all active in-game channels and dynamically creates channel commands for users so that they can - just give the channek's key or alias to write to it. Whenever a + just give the channel's key or alias to write to it. Whenever a new channel is created in the database, the update() method on this handler must be called to sync it with the database (this is done automatically if creating the channel with diff --git a/evennia/comms/models.py b/evennia/comms/models.py index e66f00e813..c358cf352b 100644 --- a/evennia/comms/models.py +++ b/evennia/comms/models.py @@ -527,7 +527,6 @@ class SubscriptionHandler(object): for subscriber in make_iter(entity): if subscriber: clsname = subscriber.__dbclass__.__name__ - print("subscriber:", subscriber, clsname) # DEBUG # chooses the right type if clsname == "ObjectDB": self.obj.db_object_subscriptions.add(subscriber) @@ -585,9 +584,7 @@ class SubscriptionHandler(object): for obj in self.all(): from django.core.exceptions import ObjectDoesNotExist try: - if hasattr(obj, 'account'): - if not obj.account: - continue + if hasattr(obj, 'account') and obj.account: obj = obj.account if not obj.is_connected: continue diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 9e4d8d588a..037f3ff019 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -210,6 +210,14 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): def sessions(self): return ObjectSessionHandler(self) + @property + def is_connected(self): + # we get an error for objects subscribed to channels without this + if self.account: # seems sane to pass on the account + return self.account.is_connected + else: + return False + @property def has_account(self): """ @@ -553,6 +561,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): obj.at_msg_send(text=text, to_obj=self, **kwargs) except Exception: logger.log_trace() + kwargs["options"] = options try: if not self.at_msg_receive(text=text, **kwargs): # if at_msg_receive returns false, we abort message to this object @@ -560,7 +569,6 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): except Exception: logger.log_trace() - kwargs["options"] = options if text and not (isinstance(text, basestring) or isinstance(text, tuple)): # sanitize text before sending across the wire diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 84a21c0c27..3f8b4cd742 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -282,6 +282,8 @@ class AttributeHandler(object): "attribute__db_attrtype": self._attrtype, "attribute__db_key__iexact": key.lower(), "attribute__db_category__iexact": category.lower() if category else None} + if not self.obj.pk: + return [] conn = getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query) if conn: attr = conn[0].attribute diff --git a/evennia/typeclasses/managers.py b/evennia/typeclasses/managers.py index 67eb9e065b..25488f4987 100644 --- a/evennia/typeclasses/managers.py +++ b/evennia/typeclasses/managers.py @@ -8,6 +8,8 @@ import shlex from django.db.models import Q from evennia.utils import idmapper from evennia.utils.utils import make_iter, variable_from_module, to_unicode +from evennia.typeclasses.attributes import Attribute +from evennia.typeclasses.tags import Tag __all__ = ("TypedObjectManager", ) _GA = object.__getattribute__ @@ -56,17 +58,19 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): dbmodel = self.model.__dbclass__.__name__.lower() query = [("attribute__db_attrtype", attrtype), ("attribute__db_model", dbmodel)] if obj: - query.append(("%s__id" % self.model.__name__.lower(), obj.id)) + query.append(("%s__id" % self.model.__dbclass__.__name__.lower(), obj.id)) if key: query.append(("attribute__db_key", key)) if category: query.append(("attribute__db_category", category)) if strvalue: query.append(("attribute__db_strvalue", strvalue)) - elif value: - # strvalue and value are mutually exclusive + if value: + # no reason to make strvalue/value mutually exclusive at this level query.append(("attribute__db_value", value)) - return [th.attribute for th in self.model.db_attributes.through.objects.filter(**dict(query))] + return Attribute.objects.filter( + pk__in=self.model.db_attributes.through.objects.filter( + **dict(query)).values_list("attribute_id", flat=True)) def get_nick(self, key=None, category=None, value=None, strvalue=None, obj=None): """ @@ -189,7 +193,9 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager): query.append(("tag__db_key", key)) if category: query.append(("tag__db_category", category)) - return [th.tag for th in self.model.db_tags.through.objects.filter(**dict(query))] + return Tag.objects.filter( + pk__in=self.model.db_tags.through.objects.filter( + **dict(query)).values_list("tag_id", flat=True)) def get_permission(self, key=None, category=None, obj=None): """ diff --git a/evennia/web/webclient/templates/webclient/base.html b/evennia/web/webclient/templates/webclient/base.html index 30a68c498f..863a90ba11 100644 --- a/evennia/web/webclient/templates/webclient/base.html +++ b/evennia/web/webclient/templates/webclient/base.html @@ -25,7 +25,7 @@ JQuery available. @@ -57,6 +57,12 @@ JQuery available. {% endblock %} + +