Moved object methods up from ObjectDB and mainly onto the typeclass. In the process of converting players in the same way.

This commit is contained in:
Griatch 2014-12-24 01:24:26 +01:00
parent 302f5bdd81
commit db512cbbf5
6 changed files with 175 additions and 226 deletions

View file

@ -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.

View file

@ -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):

View file

@ -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):
"""

View file

@ -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()")

View file

@ -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

View file

@ -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,