From db512cbbf541fa6ec09e09f0351c3e68a00e8198 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 24 Dec 2014 01:24:26 +0100 Subject: [PATCH] Moved object methods up from ObjectDB and mainly onto the typeclass. In the process of converting players in the same way. --- src/objects/objects.py | 1 + src/players/models.py | 87 ++------------- src/players/player.py | 213 +++++++++++++++++++++--------------- src/server/initial_setup.py | 18 +-- src/typeclasses/models.py | 80 +++++--------- src/utils/dbserialize.py | 2 +- 6 files changed, 175 insertions(+), 226 deletions(-) diff --git a/src/objects/objects.py b/src/objects/objects.py index 76b3693b9f..5d1818755e 100644 --- a/src/objects/objects.py +++ b/src/objects/objects.py @@ -823,6 +823,7 @@ class DefaultObject(ObjectDB): return True # methods inherited from the typeclass system + def __eq__(self, other): """ Checks for equality against an id string or another object or user. diff --git a/src/players/models.py b/src/players/models.py index e16c589792..951c097126 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -24,12 +24,9 @@ from django.utils.encoding import smart_str from src.players.manager import PlayerDBManager from src.scripts.models import ScriptDB from src.typeclasses.models import TypedObject -from src.typeclasses.attributes import NickHandler -from src.scripts.scripthandler import ScriptHandler -from src.commands.cmdsethandler import CmdSetHandler from src.commands import cmdhandler from src.utils import utils, logger -from src.utils.utils import to_str, make_iter, lazy_property +from src.utils.utils import to_str, make_iter from django.utils.translation import ugettext as _ @@ -111,20 +108,6 @@ class PlayerDB(TypedObject, AbstractUser): app_label = 'players' verbose_name = 'Player' - # lazy-loading of handlers - @lazy_property - def cmdset(self): - return CmdSetHandler(self, True) - - @lazy_property - def scripts(self): - return ScriptHandler(self) - - @lazy_property - def nicks(self): - return NickHandler(self) - - # alias to the objs property def __characters_get(self): return self.objs @@ -143,7 +126,7 @@ class PlayerDB(TypedObject, AbstractUser): """ Getter. Allows for value = self.name. Returns a list of cmdset_storage. """ - storage = _GA(self, "db_cmdset_storage") + storage = self.db_cmdset_storage # we need to check so storage is not None return [path.strip() for path in storage.split(',')] if storage else [] @@ -168,20 +151,22 @@ class PlayerDB(TypedObject, AbstractUser): # def __str__(self): - return smart_str("%s(player %s)" % (_GA(self, "name"), _GA(self, "dbid"))) + return smart_str("%s(player %s)" % (self.name, self.dbid)) def __unicode__(self): - return u"%s(player#%s)" % (_GA(self, "name"), _GA(self, "dbid")) + return u"%s(player#%s)" % (self.name, self.dbid) #@property def __username_get(self): - return _GA(self, "username") + return self.username def __username_set(self, value): - _SA(self, "username", value) + self.username = value + self.save(update_fields=["username"]) def __username_del(self): - _DA(self, "username") + del self.username + # aliases name = property(__username_get, __username_set, __username_del) key = property(__username_get, __username_set, __username_del) @@ -198,64 +183,10 @@ class PlayerDB(TypedObject, AbstractUser): raise Exception("User id cannot be deleted!") uid = property(__uid_get, __uid_set, __uid_del) - #@property - #def __is_superuser_get(self): - # "Superusers have all permissions." - # return self.db_is_superuser - # #is_suser = get_prop_cache(self, "_is_superuser") - # #if is_suser == None: - # # is_suser = _GA(self, "user").is_superuser - # # set_prop_cache(self, "_is_superuser", is_suser) - # #return is_suser - #is_superuser = property(__is_superuser_get) - # # PlayerDB class access methods # - def msg(self, text=None, from_obj=None, sessid=None, **kwargs): - """ - Evennia -> User - This is the main route for sending data back to the user from the - server. - - outgoing_string (string) - text data to send - from_obj (Object/Player) - source object of message to send. Its - at_msg_send() hook will be called. - sessid - the session id of the session to send to. If not given, return - to all sessions connected to this player. This is usually only - relevant when using msg() directly from a player-command (from - a command on a Character, the character automatically stores - and handles the sessid). Can also be a list of sessids. - kwargs (dict) - All other keywords are parsed as extra data. - """ - if "data" in kwargs: - # deprecation warning - logger.log_depmsg("PlayerDB:msg() 'data'-dict keyword is deprecated. Use **kwargs instead.") - data = kwargs.pop("data") - if isinstance(data, dict): - kwargs.update(data) - - text = to_str(text, force_string=True) if text else "" - if from_obj: - # call hook - try: - _GA(from_obj, "at_msg_send")(text=text, to_obj=self, **kwargs) - except Exception: - pass - sessions = _MULTISESSION_MODE > 1 and sessid and _GA(self, "get_session")(sessid) or None - if sessions: - for session in make_iter(sessions): - obj = session.puppet - if obj and not obj.at_msg_receive(text=text, **kwargs): - # if hook returns false, cancel send - continue - session.msg(text=text, **kwargs) - else: - # if no session was specified, send to them all - for sess in _GA(self, 'get_all_sessions')(): - sess.msg(text=text, **kwargs) - # session-related methods def get_session(self, sessid): diff --git a/src/players/player.py b/src/players/player.py index 37d6169d09..dfcb90671f 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -18,7 +18,13 @@ from src.players.manager import PlayerManager from src.players.models import PlayerDB from src.comms.models import ChannelDB from src.utils import logger -__all__ = ("Player",) +from src.utils.utils import lazy_property, to_str, make_iter +from src.typeclasses.attributes import NickHandler +from src.scripts.scripthandler import ScriptHandler +from src.commands.cmdsethandler import CmdSetHandler + + +__all__ = ("DefaultPlayer",) _MULTISESSION_MODE = settings.MULTISESSION_MODE _CMDSET_PLAYER = settings.CMDSET_PLAYER @@ -26,105 +32,140 @@ _CONNECT_CHANNEL = None class DefaultPlayer(PlayerDB): """ - Base typeclass for all Players. - """ + This is the base Typeclass for all Players. Players represent + the person playing the game and tracks account info, password + etc. They are OOC entities without presence in-game. A Player + can connect to a Character Object in order to "enter" the + game. + + Player Typeclass API: + + * Available properties (only available on initiated typeclass objects) + + key (string) - name of player + name (string)- wrapper for user.username + aliases (list of strings) - aliases to the object. Will be saved to + database as AliasDB entries but returned as strings. + dbref (int, read-only) - unique #id-number. Also "id" can be used. + dbobj (Player, read-only) - link to database model. dbobj.typeclass + points back to this class + typeclass (Player, read-only) - this links back to this class as an + identified only. Use self.swap_typeclass() to switch. + date_created (string) - time stamp of object creation + permissions (list of strings) - list of permission strings + + user (User, read-only) - django User authorization object + obj (Object) - game object controlled by player. 'character' can also + be used. + sessions (list of Sessions) - sessions connected to this player + is_superuser (bool, read-only) - if the connected user is a superuser + + * Handlers + + locks - lock-handler: use locks.add() to add new lock strings + db - attribute-handler: store/retrieve database attributes on this + self.db.myattr=val, val=self.db.myattr + ndb - non-persistent attribute handler: same as db but does not + create a database entry when storing data + scripts - script-handler. Add new scripts to object with scripts.add() + cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object + nicks - nick-handler. New nicks with nicks.add(). + + * Helper methods + + msg(outgoing_string, from_obj=None, **kwargs) + swap_character(new_character, delete_old_character=False) + execute_cmd(raw_string) + search(ostring, global_search=False, attribute_name=None, + use_nicks=False, location=None, + ignore_errors=False, player=False) + is_typeclass(typeclass, exact=False) + swap_typeclass(new_typeclass, clean_attributes=False, no_default=True) + access(accessing_obj, access_type='read', default=False) + check_permstring(permstring) + + * Hook methods + + basetype_setup() + at_player_creation() + + - note that the following hooks are also found on Objects and are + usually handled on the character level: + + at_init() + at_access() + at_cmdset_get(**kwargs) + at_first_login() + at_post_login(sessid=None) + at_disconnect() + at_message_receive() + at_message_send() + at_server_reload() + at_server_shutdown() + + """ + __metaclass__ = TypeclassBase objects = PlayerManager() - def __init__(self, *args, **kwargs): - """ - This is the base Typeclass for all Players. Players represent - the person playing the game and tracks account info, password - etc. They are OOC entities without presence in-game. A Player - can connect to a Character Object in order to "enter" the - game. + # properties + @lazy_property + def cmdset(self): + return CmdSetHandler(self, True) - Player Typeclass API: + @lazy_property + def scripts(self): + return ScriptHandler(self) - * Available properties (only available on initiated typeclass objects) + @lazy_property + def nicks(self): + return NickHandler(self) - key (string) - name of player - name (string)- wrapper for user.username - aliases (list of strings) - aliases to the object. Will be saved to - database as AliasDB entries but returned as strings. - dbref (int, read-only) - unique #id-number. Also "id" can be used. - dbobj (Player, read-only) - link to database model. dbobj.typeclass - points back to this class - typeclass (Player, read-only) - this links back to this class as an - identified only. Use self.swap_typeclass() to switch. - date_created (string) - time stamp of object creation - permissions (list of strings) - list of permission strings - - user (User, read-only) - django User authorization object - obj (Object) - game object controlled by player. 'character' can also - be used. - sessions (list of Sessions) - sessions connected to this player - is_superuser (bool, read-only) - if the connected user is a superuser - - * Handlers - - locks - lock-handler: use locks.add() to add new lock strings - db - attribute-handler: store/retrieve database attributes on this - self.db.myattr=val, val=self.db.myattr - ndb - non-persistent attribute handler: same as db but does not - create a database entry when storing data - scripts - script-handler. Add new scripts to object with scripts.add() - cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object - nicks - nick-handler. New nicks with nicks.add(). - - * Helper methods - - msg(outgoing_string, from_obj=None, **kwargs) - swap_character(new_character, delete_old_character=False) - execute_cmd(raw_string) - search(ostring, global_search=False, attribute_name=None, - use_nicks=False, location=None, - ignore_errors=False, player=False) - is_typeclass(typeclass, exact=False) - swap_typeclass(new_typeclass, clean_attributes=False, no_default=True) - access(accessing_obj, access_type='read', default=False) - check_permstring(permstring) - - * Hook methods - - basetype_setup() - at_player_creation() - - - note that the following hooks are also found on Objects and are - usually handled on the character level: - - at_init() - at_access() - at_cmdset_get(**kwargs) - at_first_login() - at_post_login(sessid=None) - at_disconnect() - at_message_receive() - at_message_send() - at_server_reload() - at_server_shutdown() - - """ - super(DefaultPlayer, self).__init__(*args, **kwargs) ## methods inherited from database model def msg(self, text=None, from_obj=None, sessid=None, **kwargs): """ Evennia -> User - This is the main route for sending data back to the user from - the server. + This is the main route for sending data back to the user from the + server. - text (string) - text data to send - from_obj (Object/DefaultPlayer) - source object of message to send - sessid - the session id of the session to send to. If not given, - return to all sessions connected to this player. This is usually only - relevant when using msg() directly from a player-command (from - a command on a Character, the character automatically stores and - handles the sessid). - kwargs - extra data to send through protocol + outgoing_string (string) - text data to send + from_obj (Object/Player) - source object of message to send. Its + at_msg_send() hook will be called. + sessid - the session id of the session to send to. If not given, return + to all sessions connected to this player. This is usually only + relevant when using msg() directly from a player-command (from + a command on a Character, the character automatically stores + and handles the sessid). Can also be a list of sessids. + kwargs (dict) - All other keywords are parsed as extra data. """ - super(DefaultPlayer, self).msg(text=text, from_obj=from_obj, sessid=sessid, **kwargs) + if "data" in kwargs: + # deprecation warning + logger.log_depmsg("PlayerDB:msg() 'data'-dict keyword is deprecated. Use **kwargs instead.") + data = kwargs.pop("data") + if isinstance(data, dict): + kwargs.update(data) + + text = to_str(text, force_string=True) if text else "" + if from_obj: + # call hook + try: + from_obj.at_msg_send(text=text, to_obj=self, **kwargs) + except Exception: + pass + sessions = _MULTISESSION_MODE > 1 and sessid and self.get_session(sessid) or None + if sessions: + for session in make_iter(sessions): + obj = session.puppet + if obj and not obj.at_msg_receive(text=text, **kwargs): + # if hook returns false, cancel send + continue + session.msg(text=text, **kwargs) + else: + # if no session was specified, send to them all + for sess in self.get_all_sessions(): + sess.msg(text=text, **kwargs) def swap_character(self, new_character, delete_old_character=False): """ diff --git a/src/server/initial_setup.py b/src/server/initial_setup.py index 43aed52487..8ac302bb84 100644 --- a/src/server/initial_setup.py +++ b/src/server/initial_setup.py @@ -10,6 +10,7 @@ import django from django.conf import settings from django.contrib.auth import get_user_model from django.utils.translation import ugettext as _ +from src.players.models import PlayerDB from src.server.models import ServerConfig from src.utils import create from src.utils.utils import class_from_module @@ -26,15 +27,14 @@ def get_god_player(): """ Creates the god user. """ - Player = class_from_module(settings.BASE_PLAYER_TYPECLASS) try: - god_player = Player.objects.get(id=1) - except Player.DoesNotExist: - txt = "\n\nNo superuser exists yet. The superuser is the 'owner'" - txt += "\account on the Evennia server. Create a new superuser using" - txt += "\nthe command" - txt += "\n\n python manage.py createsuperuser" - txt += "\n\nFollow the prompts, then restart the server." + god_player = PlayerDB.objects.get(id=1) + except PlayerDB.DoesNotExist: + txt = "\n\nNo superuser exists yet. The superuser is the 'owner'\n" \ + "account on the Evennia server. Create a new superuser using\n" \ + "the command\n\n" \ + " python manage.py createsuperuser\n\n" \ + "Follow the prompts, then restart the server." raise Exception(txt) return god_player @@ -56,7 +56,7 @@ def create_objects(): # run all creation hooks on god_player (we must do so manually # since the manage.py command does not) - god_player.typeclass_path = player_typeclass + god_player.swap_typeclass(player_typeclass, clean_attributes=True) god_player.basetype_setup() god_player.at_player_creation() god_player.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all()") diff --git a/src/typeclasses/models.py b/src/typeclasses/models.py index 3cb3445ed9..588cc962a0 100644 --- a/src/typeclasses/models.py +++ b/src/typeclasses/models.py @@ -56,6 +56,8 @@ TICKER_HANDLER = None _PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] _TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE +_GA = object.__getattribute__ +_SA = object.__setattr__ #------------------------------------------------------------ # @@ -150,16 +152,12 @@ class TypedObject(SharedMemoryModel): # Main identifier of the object, for searching. Is accessed with self.key # or self.name db_key = models.CharField('key', max_length=255, db_index=True) - # This is the python path to the type class this object is tied to the + # This is the python path to the type class this object is tied to. The # typeclass is what defines what kind of Object this is) db_typeclass_path = models.CharField('typeclass', max_length=255, null=True, help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.") # Creation date. This is not changed once the object is created. db_date_created = models.DateTimeField('creation date', editable=False, auto_now_add=True) - # Permissions (access these through the 'permissions' property) - #db_permissions = models.CharField('permissions', max_length=255, blank=True, - # help_text="a comma-separated list of text strings checked by - # in-game locks. They are often used for hierarchies, such as letting a Player have permission 'Wizards', 'Builders' etc. Character objects use 'Players' by default. Most other objects don't have any permissions.") # Lock storage db_lock_storage = models.TextField('locks', blank=True, help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.") @@ -178,6 +176,11 @@ class TypedObject(SharedMemoryModel): # typeclass mechanism def __init__(self, *args, **kwargs): + """ + This is the main function of the typeclass system - + to dynamically re-apply a class based on the + db_typeclass_path rather than use the one in the model. + """ typeclass_path = kwargs.pop("typeclass", None) super(TypedObject, self).__init__(*args, **kwargs) if typeclass_path: @@ -250,8 +253,6 @@ class TypedObject(SharedMemoryModel): # # - _typeclass_paths = settings.OBJECT_TYPECLASS_PATHS - def __eq__(self, other): return other and hasattr(other, 'dbid') and self.dbid == other.dbid @@ -332,16 +333,13 @@ class TypedObject(SharedMemoryModel): boolean True/False depending on if the swap worked or not. """ - if callable(new_typeclass): - # this is an actual class object - build the path - cls = new_typeclass - new_typeclass = "%s.%s" % (cls.__module__, cls.__name__) - else: - new_typeclass = "%s" % to_str(new_typeclass) - # Try to set the new path - # this will automatically save to database - old_typeclass_path = self.typeclass_path + if not callable(new_typeclass): + # this is an actual class object - build the path + new_typeclass = class_from_module(new_typeclass) + + # if we get to this point, the class is ok. + if inherits_from(self, "src.scripts.models.ScriptDB"): if self.interval > 0: @@ -349,15 +347,7 @@ class TypedObject(SharedMemoryModel): "Script '%s'.\nStop and start a new Script of the " \ "right type instead." % self.key) - self.typeclass_path = new_typeclass.strip() - # this will automatically use a default class if - # there is an error with the given typeclass. - new_typeclass = self - if self.typeclass_path != new_typeclass.path and no_default: - # something went wrong; the default was loaded instead, - # and we don't allow that; instead we return to previous. - self.typeclass_path = old_typeclass_path - return False + self.typeclass_path = new_typeclass.path if clean_attributes: # Clean out old attributes @@ -373,21 +363,7 @@ class TypedObject(SharedMemoryModel): self.nattributes.clear() if run_start_hooks: - # run hooks for this new typeclass - if inherits_from(self, "src.objects.models.ObjectDB"): - new_typeclass.basetype_setup() - new_typeclass.at_object_creation() - elif inherits_from(self, "src.players.models.PlayerDB"): - new_typeclass.basetype_setup() - new_typeclass.at_player_creation() - elif inherits_from(self, "src.scripts.models.ScriptDB"): - new_typeclass.at_script_creation() - new_typeclass.start() - elif inherits_from(self, "src.channels.models.Channel"): - # channels do no initial setup - pass - - return True + self.at_instance_creation() # # Lock / permission methods @@ -493,25 +469,25 @@ class TypedObject(SharedMemoryModel): class DbHolder(object): "Holder for allowing property access of attributes" def __init__(self, obj): - self.attrhandler = obj.attributes + _SA(self, "attrhandler", obj.attributes) def __getattribute__(self, attrname): if attrname == 'all': # we allow to overload our default .all - attr = self.attrhandler.get("all") + attr = _GA(self, "attrhandler").get("all") if attr: return attr return self.all - return self.attrhandler.get(attrname) + return _GA(self, "attrhandler").get(attrname) def __setattr__(self, attrname, value): - self.attrhandler.add(attrname, value) + _GA(self, "attrhandler").add(attrname, value) def __delattr__(self, attrname): - self.attrhandler.remove(attrname) + _GA(self, "attrhandler").remove(attrname) def get_all(self): - return self.attrhandler.all() + return _GA(self, "attrhandler").all() all = property(get_all) self._db_holder = DbHolder(self) return self._db_holder @@ -547,25 +523,25 @@ class TypedObject(SharedMemoryModel): class NDbHolder(object): "Holder for allowing property access of attributes" def __init__(self, obj): - self.nattrhandler = obj.nattributes + _SA(self, "nattrhandler", obj.nattributes) def __getattribute__(self, attrname): if attrname == 'all': # we allow to overload our default .all - attr = self.nattrhandler.get('all') + attr = _GA(self, "nattrhandler").get('all') if attr: return attr return self.all - return self.nattrhandler.get(attrname) + return _GA(self, "nattrhandler").get(attrname) def __setattr__(self, attrname, value): - self.nattrhandler.add(attrname, value) + _GA(self, "nattrhandler").add(attrname, value) def __delattr__(self, attrname): - self.nattrhandler.remove(attrname) + _GA(self, "nattrhandler").remove(attrname) def get_all(self): - return self.nattrhandler.all() + return _GA(self, "nattrhandler").all() all = property(get_all) self._ndb_holder = NDbHolder(self) return self._ndb_holder diff --git a/src/utils/dbserialize.py b/src/utils/dbserialize.py index 44eea29a19..bc4d292951 100644 --- a/src/utils/dbserialize.py +++ b/src/utils/dbserialize.py @@ -216,7 +216,7 @@ def pack_dbobj(item): _init_globals() obj = hasattr(item, 'dbobj') and item.dbobj or item natural_key = _FROM_MODEL_MAP[hasattr(obj, "id") and hasattr(obj, "db_date_created") and - hasattr(obj, '__class__') and obj.__class__.__name__.lower()] + hasattr(obj, '__dbclass__') and obj.__dbclass__.__name__.lower()] # build the internal representation as a tuple # ("__packed_dbobj__", key, creation_time, id) return natural_key and ('__packed_dbobj__', natural_key,