From 0061f884ae628bbd571930d23b0e566bee6788bc Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 12 Jul 2013 14:44:49 +0200 Subject: [PATCH] Implemented NickHandler, AliasHandler and TagHandler in the typeclass to replace the old handlers. Some errors during login. --- src/commands/default/comms.py | 2 +- src/objects/models.py | 112 ++-------- src/players/models.py | 56 ++--- src/scripts/models.py | 8 +- src/typeclasses/managers.py | 13 +- ...copy_nicks_to_liteattrs_aliases_to_tags.py | 6 +- src/typeclasses/models.py | 201 +++++++++++------- 7 files changed, 176 insertions(+), 222 deletions(-) diff --git a/src/commands/default/comms.py b/src/commands/default/comms.py index b886306c4..4bee66e81 100644 --- a/src/commands/default/comms.py +++ b/src/commands/default/comms.py @@ -160,7 +160,7 @@ class CmdDelCom(MuxPlayerCommand): if not channel: self.msg("No channel with alias '%s' was found." % ostring) else: - if caller.nicks.has(ostring, nick_type="channel"): + if caller.nicks.get(ostring, nick_type="channel", default=False): caller.nicks.delete(ostring, nick_type="channel") self.msg("Your alias '%s' for channel %s was cleared." % (ostring, channel.key)) else: diff --git a/src/objects/models.py b/src/objects/models.py index e502096c2..c7dccb785 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -18,13 +18,11 @@ import traceback from django.db import models from django.conf import settings -from src.utils.idmapper.models import SharedMemoryModel -from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler +from src.typeclasses.models import TypedObject, TagHandler, NickHandler, AliasHandler from src.server.caches import get_field_cache, set_field_cache, del_field_cache -from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache +from src.server.caches import get_prop_cache, set_prop_cache from src.typeclasses.typeclass import TypeClass -#from src.players.models import PlayerNick from src.objects.manager import ObjectManager from src.players.models import PlayerDB from src.commands.cmdsethandler import CmdSetHandler @@ -35,7 +33,7 @@ from src.utils.utils import make_iter, to_unicode, variable_from_module, inherit from django.utils.translation import ugettext as _ -#__all__ = ("Alias", "ObjectNick", "ObjectDB") +#__all__ = ("ObjectDB", ) _ScriptDB = None _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) @@ -48,63 +46,6 @@ _ME = _("me") _SELF = _("self") _HERE = _("here") -#------------------------------------------------------------ -# -# Alias -# -#------------------------------------------------------------ - -#class Alias(SharedMemoryModel): -# """ -# This model holds a range of alternate names for an object. -# These are intrinsic properties of the object. The split -# is so as to allow for effective global searches also by -# alias. -# """ -# db_key = models.CharField('alias', max_length=255, db_index=True) -# db_obj = models.ForeignKey("ObjectDB", verbose_name='object') -# -# class Meta: -# "Define Django meta options" -# verbose_name = "Object alias" -# verbose_name_plural = "Object aliases" -# def __unicode__(self): -# return u"%s" % self.db_key -# def __str__(self): -# return str(self.db_key) -# -# -# -##------------------------------------------------------------ -## -## Object Nicks -## -##------------------------------------------------------------ -# -#class ObjectNick(TypeNick): -# """ -# -# The default nick types used by Evennia are: -# inputline (default) - match against all input -# player - match against player searches -# obj - match against object searches -# channel - used to store own names for channels -# """ -# db_obj = models.ForeignKey("ObjectDB", verbose_name='object') -# -# class Meta: -# "Define Django meta options" -# verbose_name = "Nickname for Objects" -# verbose_name_plural = "Nicknames for Objects" -# unique_together = ("db_nick", "db_type", "db_obj") -# -#class ObjectNickHandler(TypeNickHandler): -# """ -# Handles nick access and setting. Accessed through ObjectDB.nicks -# """ -# NickClass = ObjectNick - - #------------------------------------------------------------ # # ObjectDB @@ -199,7 +140,9 @@ class ObjectDB(TypedObject): _SA(self, "cmdset", CmdSetHandler(self)) _GA(self, "cmdset").update(init_mode=True) _SA(self, "scripts", ScriptHandler(self)) - #_SA(self, "nicks", ObjectNickHandler(self)) + _SA(self, "tags", TagHandler(self, "object")) + _SA(self, "aliases", AliasHandler(self, "object")) + _SA(self, "nicks", NickHandler(self, "object")) # Wrapper properties to easily set database fields. These are # @property decorators that allows to access these fields using @@ -209,30 +152,6 @@ class ObjectDB(TypedObject): # value = self.attr and del self.attr respectively (where self # is the object in question). - # aliases property (wraps (db_aliases) - #@property - def __aliases_get(self): - "Getter. Allows for value = self.aliases" - aliases = get_prop_cache(self, "_aliases") - if aliases == None: - aliases = list(Alias.objects.filter(db_obj=self).values_list("db_key", flat=True)) - set_prop_cache(self, "_aliases", aliases) - return aliases - #@aliases.setter - def __aliases_set(self, aliases): - "Setter. Allows for self.aliases = value" - for alias in make_iter(aliases): - new_alias = Alias(db_key=alias, db_obj=self) - new_alias.save() - set_prop_cache(self, "_aliases", make_iter(aliases)) - #@aliases.deleter - def __aliases_del(self): - "Deleter. Allows for del self.aliases" - for alias in Alias.objects.filter(db_obj=self): - alias.delete() - #del_prop_cache(self, "_aliases") - aliases = property(__aliases_get, __aliases_set, __aliases_del) - # player property (wraps db_player) #@property def __player_get(self): @@ -643,12 +562,12 @@ class ObjectDB(TypedObject): return self.typeclass if use_nicks: - nick = None nicktype = "object" - # look up nicks - nicks = ObjectNick.objects.filter(db_obj=self, db_type=nicktype) + # get all valid nicks to search + nicks = self.nicks.get(category="object_nick_%s" % nicktype) if self.has_player: - nicks = list(nicks) + list(PlayerNick.objects.filter(db_obj=self.db_player, db_type=nicktype)) + pnicks = self.nicks.get(category="player_nick_%s" % nicktype) + nicks = nicks + pnicks for nick in nicks: if searchdata == nick.db_nick: searchdata = nick.db_real @@ -723,12 +642,15 @@ class ObjectDB(TypedObject): raw_list = raw_string.split(None) raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]] - nicks = ObjectNick.objects.filter(db_obj=self, db_type__in=("inputline", "channel")) + # fetch the nick data efficiently + nicks = self.db_lnattributes.filter(db_category__in=("object_nick_inputline", "object_nick_channel")).prefetch_related("db_key","db_data") if self.has_player: - nicks = list(nicks) + list(PlayerNick.objects.filter(db_obj=self.db_player, db_type__in=("inputline","channel"))) + pnicks = self.player.db_lnattributes.filter( + db_category__in=("player_nick_inputline", "player_nick_channel")).prefetch_related("db_key","db_data") + nicks = nicks + pnicks for nick in nicks: - if nick.db_nick in raw_list: - raw_string = raw_string.replace(nick.db_nick, nick.db_real, 1) + if nick.db_key in raw_list: + raw_string = raw_string.replace(nick.db_key, nick.db_data, 1) break return cmdhandler.cmdhandler(_GA(self, "typeclass"), raw_string, sessid=sessid) diff --git a/src/players/models.py b/src/players/models.py index ee42972d6..af7d0f7f6 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -25,25 +25,21 @@ account info and OOC account configuration variables etc. from django.conf import settings from django.db import models -from django.contrib.auth.models import AbstractUser, User +from django.contrib.auth.models import AbstractUser from django.utils.encoding import smart_str -from django.db.models.signals import post_init, pre_delete -from src.server.caches import get_field_cache, set_field_cache, del_field_cache -from src.server.caches import get_prop_cache, set_prop_cache, del_prop_cache +from src.server.caches import get_field_cache, set_field_cache from src.players import manager from src.scripts.models import ScriptDB -from src.typeclasses.models import Attribute, TypedObject, TypeNick, TypeNickHandler -from src.typeclasses.typeclass import TypeClass +from src.typeclasses.models import TypedObject, TagHandler, NickHandler, AliasHandler from src.commands.cmdsethandler import CmdSetHandler from src.commands import cmdhandler -from src.utils import logger, utils -from src.utils.utils import inherits_from, make_iter +from src.utils import utils from django.utils.translation import ugettext as _ -__all__ = ("PlayerNick", "PlayerDB") +__all__ = ("PlayerDB",) _ME = _("me") _SELF = _("self") @@ -59,35 +55,6 @@ _DA = object.__delattr__ _TYPECLASS = None -##------------------------------------------------------------ -## -## Player Nicks -## -##------------------------------------------------------------ -# -#class PlayerNick(TypeNick): -# """ -# -# The default nick types used by Evennia are: -# inputline (default) - match against all input -# player - match against player searches -# obj - match against object searches -# channel - used to store own names for channels -# """ -# db_obj = models.ForeignKey("PlayerDB", verbose_name="player") -# -# class Meta: -# "Define Django meta options" -# verbose_name = "Nickname for Players" -# verbose_name_plural = "Nicknames Players" -# unique_together = ("db_nick", "db_type", "db_obj") -# -#class PlayerNickHandler(TypeNickHandler): -# """ -# Handles nick access and setting. Accessed through ObjectDB.nicks -# """ -# NickClass = PlayerNick - #------------------------------------------------------------ # @@ -153,7 +120,9 @@ class PlayerDB(TypedObject, AbstractUser): # handlers _SA(self, "cmdset", CmdSetHandler(self)) _GA(self, "cmdset").update(init_mode=True) - #_SA(self, "nicks", PlayerNickHandler(self)) + _SA(self, "tags", TagHandler(self, "player")) + _SA(self, "aliases", AliasHandler(self, "player")) + _SA(self, "nicks", NickHandler(self, "player")) # Wrapper properties to easily set database fields. These are # @property decorators that allows to access these fields using @@ -520,9 +489,12 @@ class PlayerDB(TypedObject, AbstractUser): raw_list = raw_string.split(None) raw_list = [" ".join(raw_list[:i+1]) for i in range(len(raw_list)) if raw_list[:i+1]] - for nick in PlayerNick.objects.filter(db_obj=self, db_type__in=("inputline","channel")): - if nick.db_nick in raw_list: - raw_string = raw_string.replace(nick.db_nick, nick.db_real, 1) + # get the nick replacement data directly from the database to be able to use db_category__in + nicks = self.db_liteattributes.filter( + db_category__in=("object_nick_inputline", "object_nick_channel")).prefetch_related("db_key","db_data") + for nick in nicks: + if nick.db_key in raw_list: + raw_string = raw_string.replace(nick.db_key, nick.db_data, 1) break if not sessid and _MULTISESSION_MODE in (0, 1): # in this case, we should either have only one sessid, or the sessid diff --git a/src/scripts/models.py b/src/scripts/models.py index cc29d9658..9b47b3d0b 100644 --- a/src/scripts/models.py +++ b/src/scripts/models.py @@ -28,7 +28,7 @@ from django.conf import settings from django.db import models from django.db.models.signals import post_init, pre_delete -from src.typeclasses.models import Attribute, TypedObject +from src.typeclasses.models import Attribute, TypedObject, TagHandler, AliasHandler, NickHandler from django.contrib.contenttypes.models import ContentType from src.scripts.manager import ScriptManager @@ -104,6 +104,12 @@ class ScriptDB(TypedObject): "Define Django meta options" verbose_name = "Script" + def __init__(self, *args, **kwargs): + super(ScriptDB, self).__init__(self, *args, **kwargs) + _SA(self, "tags", TagHandler(self, "script")) + _SA(self, "aliases", AliasHandler(self, "script")) + + # Wrapper properties to easily set database fields. These are # @property decorators that allows to access these fields using # normal python operations (without having to remember to save() diff --git a/src/typeclasses/managers.py b/src/typeclasses/managers.py index 0ee0f0a21..ae84ce238 100644 --- a/src/typeclasses/managers.py +++ b/src/typeclasses/managers.py @@ -101,9 +101,9 @@ class LiteAttributeManager(models.Manager): if search_key or category: key_cands = Q(db_key__iexact=search_key.lower().strip()) if search_key!=None else Q() cat_cands = Q(db_category__iexact=category.lower.strip()) if search_key!=None else Q() - return _GA(obj, "db_tags").filter(cat_cands & key_cands) + return _GA(obj, "db_liteattributes").filter(cat_cands & key_cands) else: - return list(_GA(obj, "db_tags").all()) + return list(_GA(obj, "db_liteattributes").all()) def get_lattr(self, search_key=None, category=None): """ @@ -167,10 +167,17 @@ class TagManager(models.Manager): the search criteria. search_key (string) - the tag identifier category (string) - the tag category + + Returns a single Tag (or None) if both key and category is given, otherwise + it will return a list. """ key_cands = Q(db_key__iexact=search_key.lower().strip()) if search_key!=None else Q() cat_cands = Q(db_category__iexact=category.lower.strip()) if search_key!=None else Q() - return list(self.filter(key_cands & cat_cands)) + tags = self.filter(key_cands & cat_cands) + if search_key and category: + return tags[0] if tags else None + else: + return list(tags) def get_objs_with_tag(self, objclass, search_key=None, category=None): """ diff --git a/src/typeclasses/migrations/0004_copy_nicks_to_liteattrs_aliases_to_tags.py b/src/typeclasses/migrations/0004_copy_nicks_to_liteattrs_aliases_to_tags.py index 09f3eccae..f133b43d3 100644 --- a/src/typeclasses/migrations/0004_copy_nicks_to_liteattrs_aliases_to_tags.py +++ b/src/typeclasses/migrations/0004_copy_nicks_to_liteattrs_aliases_to_tags.py @@ -15,18 +15,18 @@ class Migration(DataMigration): # and orm['appname.ModelName'] for models in other applications. for alias in orm['objects.Alias'].objects.all(): # convert all Aliases to tags - tag = orm.Tag(db_key=alias.db_key, db_category="aliases", db_data=None) + tag = orm.Tag(db_key=alias.db_key, db_category="object_alias", db_data=None) tag.save() obj = alias.db_obj obj.db_tags.add(tag) # convert all nicks to LiteAttrs for nick in orm['objects.ObjectNick'].objects.all(): - lattr = orm.LiteAttribute(db_key=nick.db_nick, db_category="nick_%s" % nick.db_type, db_data=nick.db_real) + lattr = orm.LiteAttribute(db_key=nick.db_nick, db_category="object_nick_%s" % nick.db_type, db_data=nick.db_real) lattr.save() obj = nick.db_obj obj.db_liteattributes.add(lattr) for nick in orm['players.PlayerNick'].objects.all(): - lattr = orm.LiteAttribute(db_key=nick.db_nick, db_category="nick_%s" % nick.db_type, db_data=nick.db_real) + lattr = orm.LiteAttribute(db_key=nick.db_nick, db_category="player_nick_%s" % nick.db_type, db_data=nick.db_real) lattr.save() obj = nick.db_obj obj.db_liteattributes.add(lattr) diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index c7d0699b7..fe3e992eb 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -346,52 +346,104 @@ class Tag(models.Model): def __str__(self): return str(self.db_key) -#------------------------------------------------------------ + # -# Nicks +# Helper handlers # -#------------------------------------------------------------ -class TypeNick(SharedMemoryModel): +class TagHandler(object): """ - This model holds whichever alternate names this object - has for OTHER objects, but also for arbitrary strings, - channels, players etc. Setting a nick does not affect - the nicknamed object at all (as opposed to Aliases above), - and only this object will be able to refer to the nicknamed - object by the given nick. - - The default nick types used by Evennia are: - inputline (default) - match against all input - player - match against player searches - obj - match against object searches - channel - used to store own names for channels - + Generic tag-handler. Accessed via TypedObject.tags. """ - db_nick = models.CharField('nickname',max_length=255, db_index=True, help_text='the alias') - db_real = models.TextField('realname', help_text='the original string to match and replace.') - db_type = models.CharField('nick type',default="inputline", max_length=16, null=True, blank=True, - help_text="the nick type describes when the engine tries to do nick-replacement. Common options are 'inputline','player','obj' and 'channel'. Inputline checks everything being inserted, whereas the other cases tries to replace in various searches or when posting to channels.") - db_obj = None #models.ForeignKey("ObjectDB") - - class Meta: - "Define Django meta options" - abstract = True - verbose_name = "Nickname" - unique_together = ("db_nick", "db_type", "db_obj") - -class TypeNickHandler(object): - """ - Handles nick access and setting. Accessed through ObjectDB.nicks - """ - - NickClass = TypeNick - - def __init__(self, obj): + def __init__(self, obj, category_prefix=""): """ - This handler allows for accessing and setting nicks - - on-the-fly replacements for various text input passing through - this object (most often a Character) + Tags are stored internally in the TypedObject.db_tags m2m field + using the category + """ + self.obj = obj + self.prefix = category_prefix.strip().lower() if category_prefix else "" + + def add(self, tag, category=None, data=None): + "Add a new tag to the handler" + tag = tag.strip().lower() if tag!=None else None + category = "%s%s" % (self.prefix, category.strip.lower()) if category!=None else None + data = str(data) if data!=None else None + # this will only create tag if no matches existed beforehand (it will overload + # data on an existing tag since that is not considered part of making the tag unique) + tagobj = Tag.objects.create_tag(key=tag, category=category, data=data) + self.obj.db_tags.add(tagobj) + + def remove(self, tag, category=None): + "Remove a tag from the handler" + tag = tag.strip().lower() if tag!=None else None + category = "%s%s" % (self.prefix, category.strip.lower()) if category!=None else None + #TODO This does not delete the tag object itself. Maybe it should do that when no + # objects reference the tag anymore? + tagobj = self.obj.db_tags.filter(db_key=tag, db_category=category) + if tagobj: + self.obj.remove(tagobj[0]) + + def all(self): + "Get all tags in this handler" + return self.obj.db_tags.all().get_values("db_key") + + def __str__(self): + return ",".join(self.all()) + def __unicode(self): + return u",".join(self.all()) + + + +class AliasHandler(object): + """ + Handles alias access and setting. Accessed through TypedObject.aliases. + """ + def __init__(self, obj, category_prefix="object_"): + """ + Aliases are alternate names for an entity. + Implements the alias handler, using Tags for storage and + the _alias tag category. It is available + as TypedObjects.aliases. + """ + self.obj = obj + self.category = "%salias" % category_prefix + + def add(self, alias): + "Add a new nick to the handler" + if not alias or not alias.strip(): + return + alias = alias.strip() + # create a unique tag only if it didn't already exist + aliasobj = Tag.objects.create_tag(key=alias, category=self.category) + self.obj.db_tags.add(aliasobj) + + def remove(self, alias): + "Remove alias from handler." + aliasobj = Tag.objects.filter(db_key__iexact=alias.strip(), category=self.category).count() + #TODO note that this doesn't delete the tag itself. We might want to do this when no object + # uses it anymore ... + self.obj.db_tags.remove(aliasobj) + + def all(self): + "Get all aliases in this handler" + return self.obj.db_tags.filter(db_category=self.category).get_values("db_key") + + def __str__(self): + return ",".join(self.all()) + def __unicode(self): + return u",".join(self.all()) + + +class NickHandler(object): + """ + Handles nick access and setting. Accessed through TypedObject.nicks. + """ + + def __init__(self, obj, category_prefix="object_"): + """ + Nicks are alternate names an entity as of ANOTHER entity. The + engine will auto-replace nicks under circumstances dictated + by the nick category. It uses LiteAttributes for storage. The default nick types used by Evennia are: @@ -400,13 +452,11 @@ class TypeNickHandler(object): obj - match against object searches channel - used to store own names for channels - You can define other nicktypes by using the add() method of - this handler and set nick_type to whatever you want. It's then - up to you to somehow make use of this nick_type in your game - (such as for a "recog" system). - + These are all stored interally using categories + nick_inputline etc. """ self.obj = obj + self.prefix = "%snick_" % category_prefix.strip().lower() if category_prefix else "" def add(self, nick, realname, nick_type="inputline"): """ @@ -417,50 +467,47 @@ class TypeNickHandler(object): if not nick or not nick.strip(): return nick = nick.strip() - real = realname.strip() - query = self.NickClass.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type) + real = realname + nick_type = "%s%s" % (self.prefix, nick_type.strip().lower()) + query = self.obj.db_liteattributes.filter(db_key__iexact=nick, db_category__iexact=nick_type) if query.count(): old_nick = query[0] - old_nick.db_real = real + old_nick.db_data = real old_nick.save() else: - new_nick = self.NickClass(db_nick=nick, db_real=real, db_type=nick_type, db_obj=self.obj) + new_nick = LiteAttribute(db_key=nick, db_category=nick_type, db_data=real) new_nick.save() - def delete(self, nick, nick_type="inputline"): + self.obj.db_liteattributes.add(new_nick) + + def remove(self, nick, nick_type="inputline"): "Removes a previously stored nick" nick = nick.strip() - query = self.NickClass.objects.filter(db_obj=self.obj, db_nick__iexact=nick, db_type__iexact=nick_type) + nick_type = "%s%s" % (self.prefix, nick_type.strip().lower()) + query = self.obj.liteattributes.filter(db_key__iexact=nick, db_category__iexact=nick_type) if query.count(): # remove the found nick(s) query.delete() - def get(self, nick=None, nick_type="inputline", obj=None): - """ - Retrieves a given nick (with a specified nick_type) on an object. If no nick is given, returns a list - of all nicks on the object, or the empty list. - Defaults to searching the current object. - """ - if not obj: - # defaults to the current object - obj = self.obj - if nick: - query = self.NickClass.objects.filter(db_obj=obj, db_nick__iexact=nick, db_type__iexact=nick_type) - query = query.values_list("db_real", flat=True) - if query.count(): - return query[0] - else: - return nick - else: - return self.NickClass.objects.filter(db_obj=obj) - def has(self, nick, nick_type="inputline", obj=None): - """ - Returns true/false if this nick and nick_type is defined on the given - object or not. If no obj is given, default to the current object the - handler is defined on. + def delete(self, *args, **kwargs): + "alias wrapper" + self.remove(self, *args, **kwargs) + + def get(self, nick=None, nick_type="inputline", default=None): """ - if not obj: - obj = self.obj - return self.NickClass.objects.filter(db_obj=obj, db_nick__iexact=nick, db_type__iexact=nick_type).count() + Retrieves a given nick replacement based on the input nick. If + given but no matching conversion was found, returns + original input or default if given + If no nick is given, returns a list of all matching nick + objects (LiteAttributes) on the object, or the empty list. + """ + nick = nick.strip().lower() if nick!=None else None + nick_type = "%s%s" % (self.prefix, nick_type.strip().lower()) + if nick: + nicks = _GA(self.obj, "db_liteattributes").objects.filter(db_key=nick, db_category=nick_type).prefetch_related("db_data") + default = default if default!=None else nick + return nicks[0].db_data if nicks else default + else: + return list(self.obj.db_liteattributes.all()) #------------------------------------------------------------