2010-08-29 18:46:58 +00:00
"""
This is the * abstract * django models for many of the database objects
in Evennia . A django abstract ( obs , not the same as a Python metaclass ! ) is
a model which is not actually created in the database , but which only exists
for other models to inherit from , to avoid code duplication . Any model can
import and inherit from these classes .
Attributes are database objects stored on other objects . The implementing
class needs to supply a ForeignKey field attr_object pointing to the kind
2012-02-14 23:40:16 +01:00
of object being mapped . Attributes storing iterables actually store special
types of iterables named PackedList / PackedDict respectively . These make
sure to save changes to them to database - this is criticial in order to
allow for obj . db . mylist [ 2 ] = data . Also , all dbobjects are saved as
dbrefs but are also aggressively cached .
2010-08-29 18:46:58 +00:00
TypedObjects are objects ' decorated ' with a typeclass - that is , the typeclass
( which is a normal Python class implementing some special tricks with its
get / set attribute methods , allows for the creation of all sorts of different
objects all with the same database object underneath . Usually attributes are
used to permanently store things not hard - coded as field on the database object .
The admin should usually not have to deal directly with the database object
layer .
This module also contains the Managers for the respective models ; inherit from
these to create custom managers .
"""
2011-06-24 20:12:59 +00:00
import sys
2010-08-29 18:46:58 +00:00
try :
import cPickle as pickle
except ImportError :
import pickle
import traceback
from django . db import models
2011-02-28 23:43:14 +00:00
from django . conf import settings
2010-08-29 18:46:58 +00:00
from django . utils . encoding import smart_str
2011-04-05 23:28:40 +00:00
from django . contrib . contenttypes . models import ContentType
2010-08-29 18:46:58 +00:00
from src . utils . idmapper . models import SharedMemoryModel
2011-04-30 21:09:19 +00:00
from src . server . models import ServerConfig
2010-08-29 18:46:58 +00:00
from src . typeclasses import managers
2011-03-15 16:08:32 +00:00
from src . locks . lockhandler import LockHandler
2011-04-16 22:26:22 +00:00
from src . utils import logger , utils
2012-02-26 01:10:20 +01:00
from src . utils . utils import make_iter , is_iter , has_parent , to_unicode , to_str
2010-08-29 18:46:58 +00:00
2011-03-15 16:08:32 +00:00
PERMISSION_HIERARCHY = [ p . lower ( ) for p in settings . PERMISSION_HIERARCHY ]
2012-02-05 21:04:10 +01:00
CTYPEGET = ContentType . objects . get
GA = object . __getattribute__
SA = object . __setattr__
DA = object . __delattr__
2012-02-14 23:40:16 +01:00
PLOADS = pickle . loads
PDUMPS = pickle . dumps
2012-02-05 21:04:10 +01:00
2012-02-25 23:37:50 +01:00
def get_cache ( obj , name ) :
" On-model Cache handler. "
try :
return GA ( obj , " _cached_db_ %s " % name )
except AttributeError :
val = GA ( obj , " db_ %s " % name )
2012-02-26 12:43:16 +01:00
if val : SA ( obj , " _cached_db_ %s " % name , val )
2012-02-25 23:37:50 +01:00
return val
2012-02-26 01:10:20 +01:00
def set_cache ( obj , name , val ) :
" On-model Cache setter "
SA ( obj , " db_ %s " % name , val )
GA ( obj , " save " ) ( )
SA ( obj , " _cached_db_ %s " % name , val )
def del_cache ( obj , name ) :
" On-model cache deleter "
2012-02-26 12:43:16 +01:00
try :
DA ( obj , " _cached_db_ %s " % name )
except AttributeError :
pass
2010-08-29 18:46:58 +00:00
#------------------------------------------------------------
#
# Attributes
#
#------------------------------------------------------------
2012-02-15 14:27:26 +01:00
class PackedDBobject ( object ) :
2011-09-20 12:37:45 +02:00
"""
Attribute helper class .
A container for storing and easily identifying database objects in
2011-09-20 19:59:08 +02:00
the database ( which doesn ' t suppport storing db_objects directly).
2011-09-20 12:37:45 +02:00
"""
2012-02-14 23:40:16 +01:00
def __init__ ( self , ID , db_model , db_key ) :
2012-02-15 14:27:26 +01:00
self . id = ID
self . db_model = db_model
self . key = db_key
2012-02-14 23:40:16 +01:00
def __str__ ( self ) :
2012-02-15 14:27:26 +01:00
return " %s (# %s ) " % ( self . key , self . id )
2012-02-14 23:40:16 +01:00
def __unicode__ ( self ) :
2012-02-15 14:27:26 +01:00
return u " %s (# %s ) " % ( self . key , self . id )
2011-04-05 23:28:40 +00:00
2011-09-20 12:37:45 +02:00
class PackedDict ( dict ) :
"""
Attribute helper class .
A variant of dict that stores itself to the database when
updating one of its keys . This is called and handled by
Attribute . validate_data ( ) .
"""
2011-09-20 15:55:58 +02:00
def __init__ ( self , db_obj , * args , * * kwargs ) :
2011-09-20 12:37:45 +02:00
"""
Sets up the packing dict . The db_store variable
is set by Attribute . validate_data ( ) when returned in
order to allow custom updates to the dict .
db_obj - the Attribute object storing this dict .
2012-02-24 23:22:38 +01:00
The ' parent ' property is set to ' init ' at creation ,
this stops the system from saving itself over and over
when first assigning the dict . Once initialization
is over , the Attribute from_attr ( ) method will assign
the parent ( or None , if at the root )
2012-02-14 23:40:16 +01:00
2011-09-20 12:37:45 +02:00
"""
self . db_obj = db_obj
2012-02-24 23:22:38 +01:00
self . parent = ' init '
2011-09-20 15:55:58 +02:00
super ( PackedDict , self ) . __init__ ( * args , * * kwargs )
2012-02-14 23:40:16 +01:00
def __str__ ( self ) :
return " { %s } " % " , " . join ( " %s : %s " % ( key , str ( val ) ) for key , val in self . items ( ) )
2012-02-24 23:22:38 +01:00
def save ( self ) :
" Relay save operation upwards in tree until we hit the root. "
if self . parent == ' init ' :
pass
elif self . parent :
self . parent . save ( )
else :
self . db_obj . value = self
2011-09-20 12:37:45 +02:00
def __setitem__ ( self , * args , * * kwargs ) :
2012-02-14 23:40:16 +01:00
" assign item to this dict "
2011-09-20 12:37:45 +02:00
super ( PackedDict , self ) . __setitem__ ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
def clear ( self , * args , * * kwargs ) :
" Custom clear "
super ( PackedDict , self ) . clear ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
def pop ( self , * args , * * kwargs ) :
" Custom pop "
super ( PackedDict , self ) . pop ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
def popitem ( self , * args , * * kwargs ) :
" Custom popitem "
super ( PackedDict , self ) . popitem ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
def update ( self , * args , * * kwargs ) :
" Custom update "
super ( PackedDict , self ) . update ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
class PackedList ( list ) :
"""
Attribute helper class .
A variant of list that stores itself to the database when
updating one of its keys . This is called and handled by
Attribute . validate_data ( ) .
"""
2011-09-20 15:55:58 +02:00
def __init__ ( self , db_obj , * args , * * kwargs ) :
2011-09-20 12:37:45 +02:00
"""
2012-02-14 23:40:16 +01:00
Sets up the packing list .
2011-09-20 12:37:45 +02:00
db_obj - the Attribute object storing this dict .
2012-02-24 23:22:38 +01:00
The ' parent ' property is set to ' init ' at creation ,
this stops the system from saving itself over and over
when first assigning the dict . Once initialization
is over , the Attribute from_attr ( ) method will assign
the parent ( or None , if at the root )
2011-09-20 12:37:45 +02:00
"""
self . db_obj = db_obj
2012-02-24 23:22:38 +01:00
self . parent = ' init '
2011-09-20 15:55:58 +02:00
super ( PackedList , self ) . __init__ ( * args , * * kwargs )
2012-02-14 23:40:16 +01:00
def __str__ ( self ) :
return " [ %s ] " % " , " . join ( str ( val ) for val in self )
2012-02-24 23:22:38 +01:00
def save ( self ) :
" Relay save operation upwards in tree until we hit the root. "
if self . parent == ' init ' :
pass
elif self . parent :
self . parent . save ( )
else :
self . db_obj . value = self
2011-09-20 12:37:45 +02:00
def __setitem__ ( self , * args , * * kwargs ) :
2012-02-14 23:40:16 +01:00
" Custom setitem that stores changed list to database. "
super ( PackedList , self ) . __setitem__ ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
def append ( self , * args , * * kwargs ) :
" Custom append "
super ( PackedList , self ) . append ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
def extend ( self , * args , * * kwargs ) :
" Custom extend "
super ( PackedList , self ) . extend ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
def insert ( self , * args , * * kwargs ) :
" Custom insert "
super ( PackedList , self ) . insert ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
def remove ( self , * args , * * kwargs ) :
" Custom remove "
super ( PackedList , self ) . remove ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
def pop ( self , * args , * * kwargs ) :
" Custom pop "
super ( PackedList , self ) . pop ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
def reverse ( self , * args , * * kwargs ) :
" Custom reverse "
super ( PackedList , self ) . reverse ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
def sort ( self , * args , * * kwargs ) :
" Custom sort "
super ( PackedList , self ) . sort ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2011-09-20 12:37:45 +02:00
2010-08-29 18:46:58 +00:00
class Attribute ( SharedMemoryModel ) :
"""
Abstract django model .
Attributes are things that are specific to different types of objects . For
example , a drink container needs to store its fill level , whereas an exit
needs to store its open / closed / locked / unlocked state . These are done via
attributes , rather than making different classes for each object type and
storing them directly . The added benefit is that we can add / remove
attributes on the fly as we like .
The Attribute class defines the following properties :
key - primary identifier
mode - which type of data is stored in attribute
permissions - perm strings
obj - which object the attribute is defined on
date_created - when the attribute was created
value - the data stored in the attribute
2012-02-14 23:40:16 +01:00
what is actually stored in the field is a dict
2010-08-29 18:46:58 +00:00
2012-02-14 23:40:16 +01:00
{ type : nodb | dbobj | dbiter ,
data : < data > }
where type is info for the loader , telling it if holds a single
dbobject ( dbobj ) , have to do a full scan for dbrefs ( dbiter ) or
if it is a normal Python structure without any dbobjs inside it
and can thus return it without further action ( nodb ) .
2010-08-29 18:46:58 +00:00
"""
#
# Attribute Database Model setup
#
#
# These databse fields are all set using their corresponding properties,
# named same as the field, but withtout the db_* prefix.
2012-02-06 13:18:25 +01:00
db_key = models . CharField ( ' key ' , max_length = 255 , db_index = True )
2010-08-29 18:46:58 +00:00
# access through the value property
2011-10-02 01:21:03 +02:00
db_value = models . TextField ( ' value ' , blank = True , null = True )
2011-03-15 16:08:32 +00:00
# Lock storage
2011-10-02 01:21:03 +02:00
db_lock_storage = models . CharField ( ' locks ' , max_length = 512 , blank = True )
2010-08-29 18:46:58 +00:00
# references the object the attribute is linked to (this is set
# by each child class to this abstact class)
db_obj = None # models.ForeignKey("RefencedObject")
# time stamp
2012-02-14 23:40:16 +01:00
db_date_created = models . DateTimeField ( ' date_created ' , editable = False , auto_now_add = True )
2010-08-29 18:46:58 +00:00
# Database manager
objects = managers . AttributeManager ( )
2011-04-05 23:28:40 +00:00
2011-03-15 16:08:32 +00:00
# Lock handler self.locks
def __init__ ( self , * args , * * kwargs ) :
" Initializes the parent first -important! "
SharedMemoryModel . __init__ ( self , * args , * * kwargs )
self . locks = LockHandler ( self )
2012-02-14 23:40:16 +01:00
self . no_cache = True
self . cached_value = None
2010-08-29 18:46:58 +00:00
class Meta :
" Define Django meta options "
abstract = True
2012-02-14 23:40:16 +01:00
verbose_name = " Evennia Attribute "
2010-08-29 18:46:58 +00:00
# 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()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# key property (wraps db_key)
#@property
def key_get ( self ) :
2012-02-25 23:37:50 +01:00
" Getter. Allows for value = self.key "
return get_cache ( self , " key " )
2010-08-29 18:46:58 +00:00
#@key.setter
def key_set ( self , value ) :
" Setter. Allows for self.key = value "
2012-02-26 01:10:20 +01:00
set_cache ( self , " key " , value )
2010-08-29 18:46:58 +00:00
#@key.deleter
def key_del ( self ) :
" Deleter. Allows for del self.key "
raise Exception ( " Cannot delete attribute key! " )
key = property ( key_get , key_set , key_del )
# obj property (wraps db_obj)
#@property
def obj_get ( self ) :
" Getter. Allows for value = self.obj "
2012-02-25 23:37:50 +01:00
return get_cache ( self , " db_obj " )
2010-08-29 18:46:58 +00:00
#@obj.setter
def obj_set ( self , value ) :
" Setter. Allows for self.obj = value "
2012-02-26 01:10:20 +01:00
set_cache ( self , " db_obj " , value )
2010-08-29 18:46:58 +00:00
#@obj.deleter
def obj_del ( self ) :
" Deleter. Allows for del self.obj "
self . db_obj = None
self . save ( )
2012-02-26 01:10:20 +01:00
del_cache ( self , " db_obj " )
2010-08-29 18:46:58 +00:00
obj = property ( obj_get , obj_set , obj_del )
# date_created property (wraps db_date_created)
#@property
def date_created_get ( self ) :
" Getter. Allows for value = self.date_created "
2012-02-25 23:37:50 +01:00
return get_cache ( self , " db_date_created " )
2010-08-29 18:46:58 +00:00
#@date_created.setter
def date_created_set ( self , value ) :
" Setter. Allows for self.date_created = value "
2010-09-05 14:42:09 +00:00
raise Exception ( " Cannot edit date_created! " )
2010-08-29 18:46:58 +00:00
#@date_created.deleter
def date_created_del ( self ) :
" Deleter. Allows for del self.date_created "
raise Exception ( " Cannot delete date_created! " )
date_created = property ( date_created_get , date_created_set , date_created_del )
# value property (wraps db_value)
#@property
def value_get ( self ) :
"""
2012-02-14 23:40:16 +01:00
Getter . Allows for value = self . value . Reads from cache if possible .
"""
if self . no_cache :
# re-create data from database and cache it
try :
value = self . from_attr ( PLOADS ( to_str ( self . db_value ) ) )
except pickle . UnpicklingError :
value = self . db_value
self . cached_value = value
self . no_cache = False
return value
else :
# normally the memory cache holds the latest data so no db access is needed.
return self . cached_value
2010-08-29 18:46:58 +00:00
#@value.setter
def value_set ( self , new_value ) :
2012-02-14 23:40:16 +01:00
"""
Setter . Allows for self . value = value . We make sure to cache everything .
"""
new_value = self . to_attr ( new_value )
self . cached_value = self . from_attr ( new_value )
self . no_cache = False
self . db_value = to_unicode ( PDUMPS ( to_str ( new_value ) ) )
2010-08-29 18:46:58 +00:00
self . save ( )
#@value.deleter
def value_del ( self ) :
" Deleter. Allows for del attr.value. This removes the entire attribute. "
self . delete ( )
value = property ( value_get , value_set , value_del )
2011-03-15 16:08:32 +00:00
# lock_storage property (wraps db_lock_storage)
#@property
def lock_storage_get ( self ) :
" Getter. Allows for value = self.lock_storage "
2012-02-25 23:37:50 +01:00
return get_cache ( self , " lock_storage " )
2011-03-15 16:08:32 +00:00
#@lock_storage.setter
def lock_storage_set ( self , value ) :
""" Saves the lock_storage. This is usually not called directly, but through self.lock() """
self . db_lock_storage = value
self . save ( )
#@lock_storage.deleter
def lock_storage_del ( self ) :
" Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead " " "
logger . log_errmsg ( " Lock_Storage (on %s ) cannot be deleted. Use obj.lock.delete() instead. " % self )
lock_storage = property ( lock_storage_get , lock_storage_set , lock_storage_del )
2011-08-11 21:16:35 +00:00
2011-03-15 16:08:32 +00:00
2010-08-29 18:46:58 +00:00
#
#
# Attribute methods
#
#
def __str__ ( self ) :
return smart_str ( " %s ( %s ) " % ( self . key , self . id ) )
def __unicode__ ( self ) :
return u " %s ( %s ) " % ( self . key , self . id )
2012-02-14 23:40:16 +01:00
# operators on various data
def to_attr ( self , data ) :
2010-08-29 18:46:58 +00:00
"""
2012-02-14 23:40:16 +01:00
Convert data to proper attr data format before saving
2011-09-20 12:37:45 +02:00
We have to make sure to not store database objects raw , since
this will crash the system . Instead we must store their IDs
and make sure to convert back when the attribute is read back
later .
Due to this it ' s criticial that we check all iterables
recursively , converting all found database objects to a form
the database can handle . We handle lists , tuples and dicts
( and any nested combination of them ) this way , all other
iterables are stored and returned as lists .
2012-02-14 23:40:16 +01:00
data storage format :
( simple | dbobj | iter , < data > )
where
simple - a single non - db object , like a string or number
dbobj - a single dbobj
iter - any iterable object - will be looped over recursively
to convert dbobj - > id .
"""
def iter_db2id ( item ) :
"""
recursively looping through stored iterables , replacing objects with ids .
( Python only builds nested functions once , so there is no overhead for nesting )
"""
dtype = type ( item )
if dtype in ( basestring , int , float ) : # check the most common types first, for speed
return item
elif hasattr ( item , " id " ) and hasattr ( item , " db_model_name " ) and hasattr ( item , " db_key " ) :
db_model_name = item . db_model_name
if db_model_name == " typeclass " :
db_model_name = GA ( item . dbobj , " db_model_name " )
return PackedDBobject ( item . id , db_model_name , item . db_key )
elif dtype == tuple :
return tuple ( iter_db2id ( val ) for val in item )
elif dtype in ( dict , PackedDict ) :
return dict ( ( key , iter_db2id ( val ) ) for key , val in item . items ( ) )
elif hasattr ( item , ' __iter__ ' ) :
return list ( iter_db2id ( val ) for val in item )
2011-04-05 23:28:40 +00:00
else :
2012-02-14 23:40:16 +01:00
return item
dtype = type ( data )
if dtype in ( basestring , int , float ) :
return ( " simple " , data )
elif hasattr ( data , " id " ) and hasattr ( data , " db_model_name " ) and hasattr ( data , ' db_key ' ) :
# all django models (objectdb,scriptdb,playerdb,channel,msg,typeclass)
# have the protected property db_model_name hardcoded on themselves for speed.
db_model_name = data . db_model_name
if db_model_name == " typeclass " :
# typeclass cannot help us, we want the actual child object model name
db_model_name = GA ( data . dbobj , " db_model_name " )
2012-02-15 14:27:26 +01:00
return ( " dbobj " , PackedDBobject ( data . id , db_model_name , data . db_key ) )
2012-02-14 23:40:16 +01:00
elif hasattr ( data , " __iter__ " ) :
return ( " iter " , iter_db2id ( data ) )
2011-04-05 23:28:40 +00:00
else :
2012-02-14 23:40:16 +01:00
return ( " simple " , data )
def from_attr ( self , datatuple ) :
"""
Retrieve data from a previously stored attribute . This
is always a dict with keys type and data .
datatuple comes from the database storage and has
the following format :
( simple | dbobj | iter , < data > )
where
simple - a single non - db object , like a string . is returned as - is .
dbobj - a single dbobj - id . This id is retrieved back from the database .
iter - an iterable . This is traversed iteratively , converting all found
dbobj - ids back to objects . Also , all lists and dictionaries are
returned as their PackedList / PackedDict counterparts in order to
allow in - place assignment such as obj . db . mylist [ 3 ] = val . Mylist
is then a PackedList that saves the data on the fly .
"""
# nested functions
def id2db ( data ) :
"""
Convert db - stored dbref back to object
"""
2012-02-15 14:27:26 +01:00
mclass = CTYPEGET ( model = data . db_model ) . model_class ( )
2012-02-14 23:40:16 +01:00
try :
2012-02-15 14:27:26 +01:00
return mclass . objects . dbref_search ( data . id )
2012-02-14 23:40:16 +01:00
except AttributeError :
try :
2012-02-15 14:27:26 +01:00
return mclass . objects . get ( id = data . id )
2012-02-14 23:40:16 +01:00
except mclass . DoesNotExist : # could happen if object was deleted in the interim.
return None
2012-02-24 23:22:38 +01:00
def iter_id2db ( item , parent = None ) :
2012-02-14 23:40:16 +01:00
"""
Recursively looping through stored iterables , replacing ids with actual objects .
We return PackedDict and PackedLists instead of normal lists ; this is needed in order for
the user to do dynamic saving of nested in - place , such as obj . db . attrlist [ 2 ] = 3. What is
stored in the database are however always normal python primitives .
"""
dtype = type ( item )
if dtype in ( basestring , int , float ) : # check the most common types first, for speed
return item
elif dtype == PackedDBobject :
return id2db ( item )
elif dtype == tuple :
return tuple ( [ iter_id2db ( val ) for val in item ] )
elif dtype in ( dict , PackedDict ) :
2012-02-24 23:22:38 +01:00
pdict = PackedDict ( self )
pdict . update ( dict ( zip ( [ key for key in item . keys ( ) ] ,
[ iter_id2db ( val , pdict ) for val in item . values ( ) ] ) ) )
pdict . parent = parent
return pdict
2012-02-14 23:40:16 +01:00
elif hasattr ( item , ' __iter__ ' ) :
2012-02-24 23:22:38 +01:00
plist = PackedList ( self )
plist . extend ( list ( iter_id2db ( val , plist ) for val in item ) )
plist . parent = parent
return plist
2012-02-14 23:40:16 +01:00
else :
return item
typ , data = datatuple
if typ == ' simple ' :
# single non-db objects
return data
elif typ == ' dbobj ' :
# a single stored dbobj
return id2db ( data )
elif typ == ' iter ' :
# all types of iterables
return iter_id2db ( data )
2010-08-29 18:46:58 +00:00
2011-03-15 16:08:32 +00:00
def access ( self , accessing_obj , access_type = ' read ' , default = False ) :
"""
Determines if another object has permission to access .
accessing_obj - object trying to access this one
access_type - type of access sought
default - what to return if no lock of access_type was found
"""
return self . locks . check ( accessing_obj , access_type = access_type , default = default )
2010-08-29 18:46:58 +00:00
2011-04-23 11:54:08 +00:00
#------------------------------------------------------------
#
# Nicks
#
#------------------------------------------------------------
class TypeNick ( SharedMemoryModel ) :
"""
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
"""
2011-10-02 01:21:03 +02:00
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. " )
2011-04-23 11:54:08 +00:00
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 ) :
" Setup "
self . obj = obj
def add ( self , nick , realname , nick_type = " inputline " ) :
" We want to assign a new nick "
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 )
if query . count ( ) :
old_nick = query [ 0 ]
old_nick . db_real = 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 . save ( )
def delete ( self , nick , nick_type = " inputline " ) :
" Removes a nick "
nick = nick . strip ( )
query = self . NickClass . objects . filter ( db_obj = self . obj , db_nick__iexact = nick , db_type__iexact = nick_type )
if query . count ( ) :
# remove the found nick(s)
query . delete ( )
def get ( self , nick = None , nick_type = " inputline " ) :
if nick :
query = self . NickClass . objects . filter ( db_obj = self . 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 = self . obj )
def has ( self , nick , nick_type = " inputline " ) :
" Returns true/false if this nick is defined or not "
return self . NickClass . objects . filter ( db_obj = self . obj , db_nick__iexact = nick , db_type__iexact = nick_type ) . count ( )
2010-08-29 18:46:58 +00:00
#------------------------------------------------------------
#
# Typed Objects
#
#------------------------------------------------------------
class TypedObject ( SharedMemoryModel ) :
"""
Abstract Django model .
This is the basis for a typed object . It also contains all the
mechanics for managing connected attributes .
The TypedObject has the following properties :
key - main name
name - alias for key
typeclass_path - the path to the decorating typeclass
typeclass - auto - linked typeclass
date_created - time stamp of object creation
permissions - perm strings
dbref - #id of object
db - persistent attribute storage
ndb - non - persistent attribute storage
"""
#
# TypedObject Database Model setup
#
#
# These databse fields are all set using their corresponding properties,
# named same as the field, but withtou the db_* prefix.
# Main identifier of the object, for searching. Can also
# be referenced as 'name'.
2012-02-06 13:18:25 +01:00
db_key = models . CharField ( ' key ' , max_length = 255 , db_index = True )
2010-08-29 18:46:58 +00:00
# This is the python path to the type class this object is tied to
# (the type class is what defines what kind of Object this is)
2011-10-02 01:21:03 +02:00
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. " )
2010-08-29 18:46:58 +00:00
# Creation date
2011-10-02 01:21:03 +02:00
db_date_created = models . DateTimeField ( ' creation date ' , editable = False , auto_now_add = True )
2010-08-29 18:46:58 +00:00
# Permissions (access these through the 'permissions' property)
2011-10-02 22:37:07 +02:00
db_permissions = models . CharField ( ' permissions ' , max_length = 255 , blank = True , help_text = " a comma-separated list of text strings checked by certain 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. " )
2011-03-15 16:08:32 +00:00
# Lock storage
2011-10-02 22:37:07 +02:00
db_lock_storage = models . CharField ( ' locks ' , max_length = 512 , 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. " )
2011-03-15 16:08:32 +00:00
2010-08-29 18:46:58 +00:00
# Database manager
objects = managers . TypedObjectManager ( )
2011-08-06 18:15:04 +00:00
# object cache and flags
2012-02-25 23:37:50 +01:00
_cached_typeclass = None
2011-08-06 18:15:04 +00:00
2011-03-15 16:08:32 +00:00
# lock handler self.locks
def __init__ ( self , * args , * * kwargs ) :
" We must initialize the parent first - important! "
SharedMemoryModel . __init__ ( self , * args , * * kwargs )
self . locks = LockHandler ( self )
2010-08-29 18:46:58 +00:00
class Meta :
"""
Django setup info .
"""
abstract = True
verbose_name = " Evennia Database Object "
ordering = [ ' -db_date_created ' , ' id ' , ' db_typeclass_path ' , ' db_key ' ]
# 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()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# key property (wraps db_key)
#@property
def key_get ( self ) :
" Getter. Allows for value = self.key "
2012-02-25 23:37:50 +01:00
return get_cache ( self , " key " )
2010-08-29 18:46:58 +00:00
#@key.setter
def key_set ( self , value ) :
" Setter. Allows for self.key = value "
2012-02-26 01:10:20 +01:00
set_cache ( self , " key " , value )
2010-08-29 18:46:58 +00:00
#@key.deleter
def key_del ( self ) :
" Deleter. Allows for del self.key "
raise Exception ( " Cannot delete objectdb key! " )
key = property ( key_get , key_set , key_del )
# name property (wraps db_key too - alias to self.key)
#@property
def name_get ( self ) :
" Getter. Allows for value = self.name "
2012-02-25 23:37:50 +01:00
return get_cache ( self , " key " )
2010-08-29 18:46:58 +00:00
#@name.setter
def name_set ( self , value ) :
" Setter. Allows for self.name = value "
2012-03-13 01:37:28 +01:00
set_cache ( self , " key " , value )
2010-08-29 18:46:58 +00:00
#@name.deleter
def name_del ( self ) :
" Deleter. Allows for del self.name "
raise Exception ( " Cannot delete name! " )
name = property ( name_get , name_set , name_del )
# typeclass_path property
#@property
def typeclass_path_get ( self ) :
2012-02-25 23:37:50 +01:00
" Getter. Allows for value = self.typeclass_path "
return get_cache ( self , " typeclass_path " )
2010-08-29 18:46:58 +00:00
#@typeclass_path.setter
def typeclass_path_set ( self , value ) :
" Setter. Allows for self.typeclass_path = value "
2012-02-26 01:10:20 +01:00
set_cache ( self , " typeclass_path " , value )
2010-08-29 18:46:58 +00:00
#@typeclass_path.deleter
def typeclass_path_del ( self ) :
" Deleter. Allows for del self.typeclass_path "
2011-08-06 18:15:04 +00:00
self . db_typeclass_path = " "
2010-08-29 18:46:58 +00:00
self . save ( )
2012-02-26 01:10:20 +01:00
del_cache ( self , " typeclass_path " )
2010-08-29 18:46:58 +00:00
typeclass_path = property ( typeclass_path_get , typeclass_path_set , typeclass_path_del )
# date_created property
#@property
def date_created_get ( self ) :
" Getter. Allows for value = self.date_created "
2012-02-25 23:37:50 +01:00
return get_cache ( self , " date_created " )
2010-08-29 18:46:58 +00:00
#@date_created.setter
def date_created_set ( self , value ) :
" Setter. Allows for self.date_created = value "
2010-09-05 14:42:09 +00:00
raise Exception ( " Cannot change date_created! " )
2010-08-29 18:46:58 +00:00
#@date_created.deleter
def date_created_del ( self ) :
" Deleter. Allows for del self.date_created "
raise Exception ( " Cannot delete date_created! " )
date_created = property ( date_created_get , date_created_set , date_created_del )
# permissions property
#@property
def permissions_get ( self ) :
" Getter. Allows for value = self.name. Returns a list of permissions. "
2012-02-25 23:37:50 +01:00
perms = get_cache ( self , " permissions " )
if perms :
return [ perm . strip ( ) for perm in perms . split ( ' , ' ) ]
2010-08-29 18:46:58 +00:00
return [ ]
#@permissions.setter
def permissions_set ( self , value ) :
2012-02-26 01:10:20 +01:00
" Setter. Allows for self.name = value. Stores as a comma-separated string. "
value = " , " . join ( [ utils . to_unicode ( val ) . strip ( ) for val in make_iter ( value ) ] )
set_cache ( self , " permissions " , value )
2010-08-29 18:46:58 +00:00
#@permissions.deleter
def permissions_del ( self ) :
" Deleter. Allows for del self.name "
self . db_permissions = " "
self . save ( )
2012-02-26 01:10:20 +01:00
del_cache ( self , " permissions " )
2010-08-29 18:46:58 +00:00
permissions = property ( permissions_get , permissions_set , permissions_del )
2011-03-15 16:08:32 +00:00
# lock_storage property (wraps db_lock_storage)
#@property
def lock_storage_get ( self ) :
" Getter. Allows for value = self.lock_storage "
2012-02-25 23:37:50 +01:00
return get_cache ( self , " lock_storage " )
2011-03-15 16:08:32 +00:00
#@lock_storage.setter
def lock_storage_set ( self , value ) :
""" Saves the lock_storagetodate. This is usually not called directly, but through self.lock() """
2012-02-26 01:10:20 +01:00
set_cache ( self , " lock_storage " , value )
2011-03-15 16:08:32 +00:00
#@lock_storage.deleter
def lock_storage_del ( self ) :
" Deleter is disabled. Use the lockhandler.delete (self.lock.delete) instead " " "
logger . log_errmsg ( " Lock_Storage (on %s ) cannot be deleted. Use obj.lock.delete() instead. " % self )
lock_storage = property ( lock_storage_get , lock_storage_set , lock_storage_del )
2010-08-29 18:46:58 +00:00
#
#
# TypedObject main class methods and properties
#
#
2012-02-25 23:37:50 +01:00
# these are identifiers for fast Attribute access and caching
2011-05-13 22:26:08 +00:00
typeclass_paths = settings . OBJECT_TYPECLASS_PATHS
2012-02-14 23:40:16 +01:00
attribute_class = Attribute # replaced by relevant attribute class for child
db_model_name = " typeclass " # used by attributes to safely store objects
2010-08-29 18:46:58 +00:00
def __eq__ ( self , other ) :
2010-09-19 06:57:08 +00:00
return other and hasattr ( other , ' id ' ) and self . id == other . id
2010-08-29 18:46:58 +00:00
def __str__ ( self ) :
return smart_str ( " %s " % self . key )
def __unicode__ ( self ) :
return u " %s " % self . key
def __getattribute__ ( self , propname ) :
"""
Will predominantly look for an attribute
on this object , but if not found we will
check if it might exist on the typeclass instead . Since
the typeclass refers back to the databaseobject as well , we
have to be very careful to avoid loops .
"""
try :
2012-02-05 21:04:10 +01:00
return GA ( self , propname )
2010-08-29 18:46:58 +00:00
except AttributeError :
# check if the attribute exists on the typeclass instead
# (we make sure to not incur a loop by not triggering the
# typeclass' __getattribute__, since that one would
# try to look back to this very database object.)
2012-02-05 21:04:10 +01:00
typeclass = GA ( self , ' typeclass ' )
2010-08-29 18:46:58 +00:00
if typeclass :
2012-02-05 21:04:10 +01:00
return GA ( typeclass , propname )
2011-03-20 20:44:48 +00:00
else :
raise AttributeError
2010-08-29 18:46:58 +00:00
#@property
def dbref_get ( self ) :
"""
Returns the object ' s dbref id on the form #NN.
Alternetively , use obj . id directly to get dbref
without any #.
"""
2012-02-25 23:37:50 +01:00
return " # %s " % str ( GA ( self , " id " ) )
2010-08-29 18:46:58 +00:00
dbref = property ( dbref_get )
# typeclass property
#@property
def typeclass_get ( self ) :
"""
Getter . Allows for value = self . typeclass .
The typeclass is a class object found at self . typeclass_path ;
2011-02-28 23:43:14 +00:00
it allows for extending the Typed object for all different
2010-08-29 18:46:58 +00:00
types of objects that the game needs . This property
handles loading and initialization of the typeclass on the fly .
2011-08-06 18:15:04 +00:00
2012-02-05 21:04:10 +01:00
Note : The liberal use of GA and __setattr__ ( instead
2011-08-06 18:15:04 +00:00
of normal dot notation ) is due to optimization : it avoids calling
the custom self . __getattribute__ more than necessary .
"""
2012-02-25 23:37:50 +01:00
path = GA ( self , " typeclass_path " )
typeclass = GA ( self , " _cached_typeclass " )
2011-08-06 18:15:04 +00:00
try :
2012-02-05 21:04:10 +01:00
if typeclass and GA ( typeclass , " path " ) == path :
2011-10-01 22:00:22 +02:00
# don't call at_init() when returning from cache
2011-08-06 18:15:04 +00:00
return typeclass
except AttributeError :
2011-10-01 22:00:22 +02:00
pass
2010-08-29 18:46:58 +00:00
errstring = " "
if not path :
2011-08-06 18:15:04 +00:00
# this means we should get the default obj without giving errors.
2012-02-05 21:04:10 +01:00
return GA ( self , " get_default_typeclass " ) ( cache = True , silent = True , save = True )
2011-05-13 22:26:08 +00:00
else :
# handle loading/importing of typeclasses, searching all paths.
2011-08-06 18:15:04 +00:00
# (self.typeclass_paths is a shortcut to settings.TYPECLASS_*_PATHS
2011-05-13 22:26:08 +00:00
# where '*' is either OBJECT, SCRIPT or PLAYER depending on the typed
2011-08-06 18:15:04 +00:00
# entities).
2012-02-05 21:04:10 +01:00
typeclass_paths = [ path ] + [ " %s . %s " % ( prefix , path ) for prefix in GA ( self , ' typeclass_paths ' ) ]
2011-08-06 18:15:04 +00:00
2011-05-13 22:26:08 +00:00
for tpath in typeclass_paths :
2011-08-06 18:15:04 +00:00
# try to import and analyze the result
2012-02-05 21:04:10 +01:00
typeclass = GA ( self , " _path_import " ) ( tpath )
2011-05-13 22:26:08 +00:00
if callable ( typeclass ) :
2011-08-06 18:15:04 +00:00
# we succeeded to import. Cache and return.
2012-02-05 21:04:10 +01:00
SA ( self , ' db_typeclass_path ' , tpath )
GA ( self , ' save ' ) ( )
2012-02-25 23:37:50 +01:00
SA ( self , " _cached_db_typeclass_path " , tpath )
2011-10-01 22:00:22 +02:00
typeclass = typeclass ( self )
2012-02-25 23:37:50 +01:00
SA ( self , " _cached_typeclass " , typeclass )
2011-10-01 22:00:22 +02:00
try :
typeclass . at_init ( )
except Exception :
logger . log_trace ( )
2011-08-06 18:15:04 +00:00
return typeclass
2011-05-13 22:26:08 +00:00
elif hasattr ( typeclass , ' __file__ ' ) :
errstring + = " \n %s seems to be just the path to a module. You need " % tpath
2010-08-29 18:46:58 +00:00
errstring + = " to specify the actual typeclass name inside the module too. "
2011-05-13 22:26:08 +00:00
else :
2011-08-06 18:15:04 +00:00
errstring + = " \n %s " % typeclass # this will hold a growing error message.
# If we reach this point we couldn't import any typeclasses. Return default. It's up to the calling
# method to use e.g. self.is_typeclass() to detect that the result is not the one asked for.
2012-02-05 21:04:10 +01:00
GA ( self , " _display_errmsg " ) ( errstring )
return GA ( self , " get_default_typeclass " ) ( cache = False , silent = False , save = False )
2011-02-28 23:43:14 +00:00
2010-08-29 18:46:58 +00:00
#@typeclass.deleter
2010-11-09 14:46:39 +00:00
def typeclass_del ( self ) :
2011-08-06 18:15:04 +00:00
" Deleter. Disallow ' del self.typeclass ' "
raise Exception ( " The typeclass property should never be deleted, only changed in-place! " )
2011-07-03 21:01:06 +00:00
# typeclass property
2010-08-29 18:46:58 +00:00
typeclass = property ( typeclass_get , fdel = typeclass_del )
def _path_import ( self , path ) :
"""
Import a class from a python path of the
form src . objects . object . Object
"""
errstring = " "
if not path :
# this needs not be bad, it just means
# we should use defaults.
return None
try :
modpath , class_name = path . rsplit ( ' . ' , 1 )
module = __import__ ( modpath , fromlist = [ class_name ] )
2011-06-24 20:12:59 +00:00
return module . __dict__ [ class_name ]
except ImportError :
trc = sys . exc_traceback
if not trc . tb_next :
# we separate between not finding the module, and finding a buggy one.
errstring + = " (Tried path ' %s ' ). " % path
else :
# a bug in the module is reported normally.
trc = traceback . format_exc ( )
errstring + = " \n %s Error importing ' %s ' . " % ( trc , path )
2010-08-29 18:46:58 +00:00
except KeyError :
errstring = " No class ' %s ' was found in module ' %s ' . "
errstring = errstring % ( class_name , modpath )
except Exception :
2011-08-06 18:15:04 +00:00
trc = traceback . format_exc ( )
errstring = " \n %s Exception importing ' %s ' . " % ( trc , path )
2010-08-29 18:46:58 +00:00
# return the error.
return errstring
2011-08-06 18:15:04 +00:00
def _display_errmsg ( self , message ) :
"""
Helper function to display error .
"""
infochan = None
cmessage = message
try :
from src . comms . models import Channel
infochan = settings . CHANNEL_MUDINFO
infochan = Channel . objects . get_channel ( infochan [ 0 ] )
if infochan :
cname = infochan . key
2011-09-03 10:22:19 +00:00
cmessage = " \n " . join ( [ " [ %s ]: %s " % ( cname , line ) for line in message . split ( ' \n ' ) if line ] )
cmessage = cmessage . strip ( )
infochan . msg ( cmessage )
2011-08-06 18:15:04 +00:00
else :
# no mudinfo channel is found. Log instead.
cmessage = " \n " . join ( [ " [NO MUDINFO CHANNEL]: %s " % line for line in message . split ( ' \n ' ) ] )
logger . log_errmsg ( cmessage )
2012-02-14 23:40:16 +01:00
except Exception :
2011-08-06 18:15:04 +00:00
if ServerConfig . objects . conf ( " server_starting_mode " ) :
print cmessage
else :
logger . log_trace ( cmessage )
2010-08-29 18:46:58 +00:00
2011-08-06 18:15:04 +00:00
def get_default_typeclass ( self , cache = False , silent = False , save = False ) :
"""
This is called when a typeclass fails to
load for whatever reason .
Overload this in different entities .
Default operation is to load a default typeclass .
"""
2012-02-05 21:04:10 +01:00
defpath = GA ( self , " default_typeclass_path " )
typeclass = GA ( self , " _path_import " ) ( defpath )
2011-08-06 18:15:04 +00:00
# if not silent:
# #errstring = "\n\nUsing Default class '%s'." % defpath
2012-02-05 21:04:10 +01:00
# GA(self, "_display_errmsg")(errstring)
2011-08-06 18:15:04 +00:00
if not callable ( typeclass ) :
# if typeclass still doesn't exist at this point, we're in trouble.
# fall back to hardcoded core class which is wrong for e.g. scripts/players etc.
failpath = defpath
defpath = " src.objects.objects.Object "
2012-02-05 21:04:10 +01:00
typeclass = GA ( self , " _path_import " ) ( defpath )
2011-08-06 18:15:04 +00:00
if not silent :
2011-08-11 21:16:35 +00:00
#errstring = " %s\n%s" % (typeclass, errstring)
errstring = " Default class ' %s ' failed to load. " % failpath
2011-08-06 18:15:04 +00:00
errstring + = " \n Using Evennia ' s default class ' %s ' . " % defpath
2012-02-05 21:04:10 +01:00
GA ( self , " _display_errmsg " ) ( errstring )
2011-08-06 18:15:04 +00:00
if not callable ( typeclass ) :
# if this is still giving an error, Evennia is wrongly configured or buggy
raise Exception ( " CRITICAL ERROR: The final fallback typeclass %s cannot load!! " % defpath )
2011-10-01 22:00:22 +02:00
typeclass = typeclass ( self )
2011-08-06 18:15:04 +00:00
if save :
2012-02-05 21:04:10 +01:00
SA ( self , ' db_typeclass_path ' , defpath )
GA ( self , ' save ' ) ( )
2011-08-06 18:15:04 +00:00
if cache :
2012-02-25 23:37:50 +01:00
SA ( self , " _cached_db_typeclass_path " , defpath )
2011-10-01 22:00:22 +02:00
2012-02-25 23:37:50 +01:00
SA ( self , " _cached_typeclass " , typeclass )
2011-10-01 22:00:22 +02:00
try :
typeclass . at_init ( )
except Exception :
logger . log_trace ( )
return typeclass
2011-08-06 18:15:04 +00:00
def is_typeclass ( self , typeclass , exact = False ) :
2010-08-29 18:46:58 +00:00
"""
Returns true if this object has this type
OR has a typeclass which is an subclass of
the given typeclass .
2011-08-06 18:15:04 +00:00
typeclass - can be a class object or the
python path to such an object to match against .
2010-08-29 18:46:58 +00:00
exact - returns true only if the object ' s
type is exactly this typeclass , ignoring
parents .
2011-08-06 18:15:04 +00:00
"""
try :
2012-02-25 23:37:50 +01:00
typeclass = GA ( typeclass , " path " )
2011-08-06 18:15:04 +00:00
except AttributeError :
pass
2012-02-25 23:37:50 +01:00
typeclasses = [ typeclass ] + [ " %s . %s " % ( path , typeclass ) for path in GA ( self , " typeclass_paths " ) ]
2011-08-06 18:15:04 +00:00
if exact :
2012-02-25 23:37:50 +01:00
current_path = GA ( self , " _cached_db_typeclass_path " )
return typeclass and any ( ( current_path == typec for typec in typeclasses ) )
2011-08-06 18:15:04 +00:00
else :
# check parent chain
2012-02-25 23:37:50 +01:00
return any ( ( cls for cls in self . typeclass . __class__ . mro ( )
if any ( ( " %s . %s " % ( GA ( cls , " __module__ " ) , GA ( cls , " __name__ " ) ) == typec for typec in typeclasses ) ) ) )
2010-08-29 18:46:58 +00:00
#
# Object manipulation methods
#
#
2011-10-03 23:53:23 +02:00
def swap_typeclass ( self , new_typeclass , clean_attributes = False , no_default = True ) :
2010-08-29 18:46:58 +00:00
"""
This performs an in - situ swap of the typeclass . This means
that in - game , this object will suddenly be something else .
Player will not be affected . To ' move ' a player to a different
object entirely ( while retaining this object ' s type), use
self . player . swap_object ( ) .
Note that this might be an error prone operation if the
old / new typeclass was heavily customized - your code
might expect one and not the other , so be careful to
bug test your code if using this feature ! Often its easiest
to create a new object and just swap the player over to
that one instead .
2012-03-25 16:35:22 +02:00
Arguments :
2010-08-29 18:46:58 +00:00
new_typeclass ( path / classobj ) - type to switch to
clean_attributes ( bool / list ) - will delete all attributes
stored on this object ( but not any
of the database fields such as name or
location ) . You can ' t get attributes back,
but this is often the safest bet to make
sure nothing in the new typeclass clashes
with the old one . If you supply a list ,
only those named attributes will be cleared .
2011-10-03 23:53:23 +02:00
no_default - if this is active , the swapper will not allow for
swapping to a default typeclass in case the given
one fails for some reason . Instead the old one
will be preserved .
2012-03-25 16:35:22 +02:00
Returns :
boolean True / False depending on if the swap worked or not .
2010-08-29 18:46:58 +00:00
"""
if callable ( new_typeclass ) :
# this is an actual class object - build the path
cls = new_typeclass . __class__
new_typeclass = " %s . %s " % ( cls . __module__ , cls . __name__ )
# Try to set the new path
2011-10-03 23:53:23 +02:00
# this will automatically save to database
old_typeclass_path = self . typeclass_path
self . typeclass_path = new_typeclass . strip ( )
2010-08-29 18:46:58 +00:00
# this will automatically use a default class if
# there is an error with the given typeclass.
2011-10-01 22:00:22 +02:00
new_typeclass = self . typeclass
2011-10-03 23:53:23 +02:00
if self . typeclass_path == new_typeclass . path :
# the typeclass loading worked as expected
2012-03-13 01:37:28 +01:00
DA ( self , " _cached_db_typeclass_path " )
2012-02-25 23:37:50 +01:00
SA ( self , " _cached_typeclass " , None )
2011-10-03 23:53:23 +02:00
elif no_default :
# something went wrong; the default was loaded instead,
# and we don't allow that; instead we return to previous.
2012-02-25 23:37:50 +01:00
SA ( self , " typeclass_path " , old_typeclass_path )
SA ( self , " _cached_typeclass " , None )
2011-10-03 23:53:23 +02:00
return False
2010-08-29 18:46:58 +00:00
if clean_attributes :
# Clean out old attributes
if is_iter ( clean_attributes ) :
for attr in clean_attributes :
self . attr ( attr , delete = True )
for nattr in clean_attributes :
if hasattr ( self . ndb , nattr ) :
self . nattr ( nattr , delete = True )
else :
#print "deleting attrs ..."
self . get_all_attributes ( )
for attr in self . get_all_attributes ( ) :
attr . delete ( )
for nattr in self . ndb . all ( ) :
del nattr
2011-03-20 19:45:56 +00:00
# run hooks for this new typeclass
new_typeclass . basetype_setup ( )
2010-08-29 18:46:58 +00:00
new_typeclass . at_object_creation ( )
2011-10-03 23:53:23 +02:00
return True
2010-08-29 18:46:58 +00:00
#
# Attribute handler methods
#
#
# Fully persistent attributes. You usually access these
2011-10-01 15:10:21 +02:00
# through the obj.db.attrname method.
2010-08-29 18:46:58 +00:00
# Helper methods for persistent attributes
def has_attribute ( self , attribute_name ) :
"""
See if we have an attribute set on the object .
attribute_name : ( str ) The attribute ' s name.
"""
2012-02-25 23:37:50 +01:00
return GA ( self , " attribute_class " ) . objects . filter ( db_obj = self ) . filter (
2012-02-05 21:04:10 +01:00
db_key__iexact = attribute_name ) . count ( )
2010-08-29 18:46:58 +00:00
def set_attribute ( self , attribute_name , new_value = None ) :
"""
Sets an attribute on an object . Creates the attribute if need
be .
attribute_name : ( str ) The attribute ' s name.
new_value : ( python obj ) The value to set the attribute to . If this is not
a str , the object will be stored as a pickle .
"""
attrib_obj = None
2012-02-25 23:37:50 +01:00
attrclass = GA ( self , " attribute_class " )
2012-02-05 21:04:10 +01:00
try :
2012-02-14 23:40:16 +01:00
# use old attribute
2012-02-05 21:04:10 +01:00
attrib_obj = attrclass . objects . filter (
db_obj = self ) . filter ( db_key__iexact = attribute_name ) [ 0 ]
except IndexError :
# no match; create new attribute
2012-02-14 23:40:16 +01:00
attrib_obj = attrclass ( db_key = attribute_name , db_obj = self )
2012-02-05 21:04:10 +01:00
# re-set an old attribute value
2012-02-14 23:40:16 +01:00
attrib_obj . value = new_value
2012-02-05 21:04:10 +01:00
2010-08-29 18:46:58 +00:00
def get_attribute ( self , attribute_name , default = None ) :
"""
Returns the value of an attribute on an object . You may need to
type cast the returned value from this function since the attribute
2012-02-05 21:04:10 +01:00
can be of any type . Returns default if no match is found .
2010-08-29 18:46:58 +00:00
attribute_name : ( str ) The attribute ' s name.
default : What to return if no attribute is found
"""
2012-02-05 21:04:10 +01:00
attrib_obj = default
try :
attrib_obj = self . attribute_class . objects . filter (
db_obj = self ) . filter ( db_key__iexact = attribute_name ) [ 0 ]
except IndexError :
return default
return attrib_obj . value
def get_attribute_raise ( self , attribute_name ) :
"""
Returns value of an attribute . Raises AttributeError
if no match is found .
attribute_name : ( str ) The attribute ' s name.
"""
try :
return self . attribute_class . objects . filter (
db_obj = self ) . filter ( db_key__iexact = attribute_name ) [ 0 ] . value
except IndexError :
raise AttributeError
2010-08-29 18:46:58 +00:00
def del_attribute ( self , attribute_name ) :
"""
Removes an attribute entirely .
attribute_name : ( str ) The attribute ' s name.
"""
2012-02-05 21:04:10 +01:00
try :
self . attribute_class . objects . filter (
db_obj = self ) . filter ( db_key__iexact = attribute_name ) [ 0 ] . delete ( )
except IndexError :
pass
2010-08-29 18:46:58 +00:00
2012-02-05 21:04:10 +01:00
def del_attribute_raise ( self , attribute_name ) :
"""
Removes and attribute . Raises AttributeError if
attribute is not found .
attribute_name : ( str ) The attribute ' s name.
"""
try :
self . attribute_class . objects . filter (
db_obj = self ) . filter ( db_key__iexact = attribute_name ) [ 0 ] . delete ( )
except IndexError :
raise AttributeError
2010-08-29 18:46:58 +00:00
def get_all_attributes ( self ) :
"""
Returns all attributes defined on the object .
2012-02-05 21:04:10 +01:00
"""
return list ( self . attribute_class . objects . filter ( db_obj = self ) )
2010-08-29 18:46:58 +00:00
def attr ( self , attribute_name = None , value = None , delete = False ) :
"""
This is a convenient wrapper for
get_attribute , set_attribute , del_attribute
and get_all_attributes .
If value is None , attr will act like
a getter , otherwise as a setter .
set delete = True to delete the named attribute .
Note that you cannot set the attribute
2012-02-05 21:04:10 +01:00
value to None using this method . Use set_attribute .
2010-08-29 18:46:58 +00:00
"""
if attribute_name == None :
# act as a list method
return self . get_all_attributes ( )
elif delete == True :
self . del_attribute ( attribute_name )
elif value == None :
# act as a getter.
return self . get_attribute ( attribute_name )
else :
# act as a setter
self . set_attribute ( attribute_name , value )
#@property
def db_get ( self ) :
"""
A second convenience wrapper for the the attribute methods . It
allows for the syntax
obj . db . attrname = value
and
value = obj . db . attrname
and
del obj . db . attrname
and
all_attr = obj . db . all ( ) ( if there is no attribute named ' all ' , in which
case that will be returned instead ) .
"""
try :
return self . _db_holder
except AttributeError :
class DbHolder ( object ) :
" Holder for allowing property access of attributes "
def __init__ ( self , obj ) :
2012-02-05 21:04:10 +01:00
SA ( self , ' obj ' , obj )
2010-08-29 18:46:58 +00:00
def __getattribute__ ( self , attrname ) :
if attrname == ' all ' :
# we allow for overwriting the all() method
# with an attribute named 'all'.
2012-02-05 21:04:10 +01:00
attr = GA ( self , ' obj ' ) . get_attribute ( " all " )
2010-08-29 18:46:58 +00:00
if attr :
return attr
2012-02-05 21:04:10 +01:00
return GA ( self , ' all ' )
return GA ( self , ' obj ' ) . get_attribute ( attrname )
2010-08-29 18:46:58 +00:00
def __setattr__ ( self , attrname , value ) :
2012-02-05 21:04:10 +01:00
GA ( self , ' obj ' ) . set_attribute ( attrname , value )
2010-08-29 18:46:58 +00:00
def __delattr__ ( self , attrname ) :
2012-02-05 21:04:10 +01:00
GA ( self , ' obj ' ) . del_attribute ( attrname )
2010-08-29 18:46:58 +00:00
def all ( self ) :
2012-02-05 21:04:10 +01:00
return GA ( self , ' obj ' ) . get_all_attributes ( )
2010-08-29 18:46:58 +00:00
self . _db_holder = DbHolder ( self )
return self . _db_holder
#@db.setter
def db_set ( self , value ) :
" Stop accidentally replacing the db object "
string = " Cannot assign directly to db object! "
2012-02-05 21:04:10 +01:00
string + = " Use db.attr=value instead. "
2010-08-29 18:46:58 +00:00
raise Exception ( string )
#@db.deleter
def db_del ( self ) :
" Stop accidental deletion. "
raise Exception ( " Cannot delete the db object! " )
db = property ( db_get , db_set , db_del )
#
2012-02-05 21:04:10 +01:00
# NON-PERSISTENT storage methods
2010-08-29 18:46:58 +00:00
#
def nattr ( self , attribute_name = None , value = None , delete = False ) :
"""
This is the equivalence of self . attr but for non - persistent
stores .
"""
if attribute_name == None :
# act as a list method
if callable ( self . ndb . all ) :
return self . ndb . all ( )
else :
return [ val for val in self . ndb . __dict__ . keys ( )
if not val . startswith [ ' _ ' ] ]
elif delete == True :
if hasattr ( self . ndb , attribute_name ) :
2012-02-05 21:04:10 +01:00
DA ( self . db , attribute_name )
2010-08-29 18:46:58 +00:00
elif value == None :
# act as a getter.
if hasattr ( self . ndb , attribute_name ) :
2012-02-05 21:04:10 +01:00
GA ( self . ndb , attribute_name )
2010-08-29 18:46:58 +00:00
else :
return None
else :
# act as a setter
2012-02-05 21:04:10 +01:00
SA ( self . db , attribute_name , value )
2010-08-29 18:46:58 +00:00
#@property
def ndb_get ( self ) :
"""
A non - persistent store ( ndb : NonDataBase ) . Everything stored
to this is guaranteed to be cleared when a server is shutdown .
2011-10-01 15:10:21 +02:00
Syntax is same as for the _get_db_holder ( ) method and
property , e . g . obj . ndb . attr = value etc .
2010-08-29 18:46:58 +00:00
"""
try :
return self . _ndb_holder
except AttributeError :
class NdbHolder ( object ) :
" Holder for storing non-persistent attributes. "
def all ( self ) :
return [ val for val in self . __dict__ . keys ( )
2012-02-05 21:04:10 +01:00
if not val . startswith [ ' _ ' ] ]
2011-08-11 21:16:35 +00:00
def __getattribute__ ( self , key ) :
# return None if no matching attribute was found.
try :
2012-02-05 21:04:10 +01:00
return GA ( self , key )
2011-08-11 21:16:35 +00:00
except AttributeError :
return None
2010-08-29 18:46:58 +00:00
self . _ndb_holder = NdbHolder ( )
return self . _ndb_holder
#@ndb.setter
def ndb_set ( self , value ) :
" Stop accidentally replacing the db object "
string = " Cannot assign directly to ndb object! "
string = " Use ndb.attr=value instead. "
raise Exception ( string )
#@ndb.deleter
def ndb_del ( self ) :
" Stop accidental deletion. "
raise Exception ( " Cannot delete the ndb object! " )
ndb = property ( ndb_get , ndb_set , ndb_del )
2011-09-03 10:22:19 +00:00
2012-02-05 21:04:10 +01:00
#
2011-03-15 16:08:32 +00:00
# Lock / permission methods
2012-02-05 21:04:10 +01:00
#
2011-03-15 16:08:32 +00:00
def access ( self , accessing_obj , access_type = ' read ' , default = False ) :
"""
Determines if another object has permission to access .
accessing_obj - object trying to access this one
access_type - type of access sought
default - what to return if no lock of access_type was found
"""
return self . locks . check ( accessing_obj , access_type = access_type , default = default )
def has_perm ( self , accessing_obj , access_type ) :
" Alias to access "
logger . log_depmsg ( " has_perm() is deprecated. Use access() instead. " )
return self . access ( accessing_obj , access_type )
def check_permstring ( self , permstring ) :
"""
2011-04-23 11:54:08 +00:00
This explicitly checks if we hold particular permission without involving
2011-03-15 16:08:32 +00:00
any locks .
"""
2011-04-23 11:54:08 +00:00
if self . player and self . player . is_superuser :
return True
2011-03-15 16:08:32 +00:00
if not permstring :
return False
perm = permstring . lower ( )
if perm in [ p . lower ( ) for p in self . permissions ] :
# simplest case - we have a direct match
return True
if perm in PERMISSION_HIERARCHY :
# check if we have a higher hierarchy position
ppos = PERMISSION_HIERARCHY . index ( perm )
return any ( True for hpos , hperm in enumerate ( PERMISSION_HIERARCHY )
if hperm in [ p . lower ( ) for p in self . permissions ] and hpos > ppos )
return False