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
2012-03-30 23:47:22 +02:00
import and inherit from these classes .
2010-08-29 18:46:58 +00:00
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
2012-03-30 23:47:22 +02:00
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
2012-02-14 23:40:16 +01:00
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
2012-03-30 23:47:22 +02:00
layer .
2010-08-29 18:46:58 +00:00
This module also contains the Managers for the respective models ; inherit from
2012-03-30 23:47:22 +02:00
these to create custom managers .
2010-08-29 18:46:58 +00:00
"""
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
2012-04-28 00:37:36 +02:00
from collections import defaultdict
2012-10-14 19:29:56 +02:00
from django . db import models , IntegrityError
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-04-15 21:46:43 +02:00
from src . utils . utils import make_iter , is_iter , to_unicode , to_str
2010-08-29 18:46:58 +00:00
2012-03-31 15:09:22 +02:00
__all__ = ( " Attribute " , " TypeNick " , " TypedObject " )
2012-03-31 13:06:29 +02:00
_PERMISSION_HIERARCHY = [ p . lower ( ) for p in settings . PERMISSION_HIERARCHY ]
2011-03-15 16:08:32 +00:00
2012-03-31 13:06:29 +02:00
_CTYPEGET = ContentType . objects . get
_GA = object . __getattribute__
_SA = object . __setattr__
_DA = object . __delattr__
_PLOADS = pickle . loads
_PDUMPS = pickle . dumps
2012-02-05 21:04:10 +01:00
2012-09-18 01:03:35 +02:00
2012-03-29 19:42:08 +02:00
# Property Cache mechanism.
2012-03-31 13:06:29 +02:00
def _get_cache ( obj , name ) :
2012-02-25 23:37:50 +01:00
" On-model Cache handler. "
try :
2012-03-31 13:06:29 +02:00
return _GA ( obj , " _cached_db_ %s " % name )
2012-03-30 23:47:22 +02:00
except AttributeError :
2012-03-31 13:06:29 +02:00
val = _GA ( obj , " db_ %s " % name )
2012-08-19 11:15:22 +02:00
_SA ( obj , " _cached_db_ %s " % name , val )
2012-03-30 23:47:22 +02:00
return val
2012-03-31 13:06:29 +02:00
def _set_cache ( obj , name , val ) :
2012-04-26 13:38:34 +02:00
" On-model Cache setter. Also updates database. "
2012-03-31 13:06:29 +02:00
_SA ( obj , " db_ %s " % name , val )
_GA ( obj , " save " ) ( )
_SA ( obj , " _cached_db_ %s " % name , val )
def _del_cache ( obj , name ) :
2012-02-26 01:10:20 +01:00
" On-model cache deleter "
2012-02-26 12:43:16 +01:00
try :
2012-03-31 13:06:29 +02:00
_DA ( obj , " _cached_db_ %s " % name )
2012-02-26 12:43:16 +01:00
except AttributeError :
2012-03-30 23:47:22 +02:00
pass
2012-09-03 01:11:14 +02:00
def _clean_cache ( obj ) :
" On-model cache resetter "
[ _DA ( obj , cname ) for cname in obj . __dict__ . keys ( ) if cname . startswith ( " _cached_db_ " ) ]
2010-08-29 18:46:58 +00:00
2012-04-28 00:37:36 +02:00
# this cache holds the attributes loaded on objects, one dictionary
# of attributes per object.
_ATTRIBUTE_CACHE = defaultdict ( dict )
2010-08-29 18:46:58 +00:00
#------------------------------------------------------------
#
2012-03-30 23:47:22 +02:00
# Attributes
2010-08-29 18:46:58 +00:00
#
#------------------------------------------------------------
2012-02-15 14:27:26 +01:00
class PackedDBobject ( object ) :
2011-09-20 12:37:45 +02:00
"""
Attribute helper class .
2012-03-30 23:47:22 +02:00
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-03-30 23:47:22 +02: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-03-30 23:47:22 +02: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 .
2012-03-30 23:47:22 +02:00
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 12:37:45 +02:00
"""
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
2012-03-30 23:47:22 +02:00
order to allow custom updates to the dict .
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 )
2012-03-30 23:47:22 +02: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 :
2012-03-30 23:47:22 +02:00
self . parent . save ( )
2012-02-24 23:22:38 +01:00
else :
2012-03-30 23:47:22 +02:00
self . db_obj . value = self
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 ( )
2012-03-30 23:47:22 +02:00
def clear ( self , * args , * * kwargs ) :
2011-09-20 12:37:45 +02:00
" Custom clear "
super ( PackedDict , self ) . clear ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2012-03-30 23:47:22 +02:00
def pop ( self , * args , * * kwargs ) :
2011-09-20 12:37:45 +02:00
" Custom pop "
super ( PackedDict , self ) . pop ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2012-03-30 23:47:22 +02:00
def popitem ( self , * args , * * kwargs ) :
2011-09-20 12:37:45 +02:00
" Custom popitem "
super ( PackedDict , self ) . popitem ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2012-03-30 23:47:22 +02:00
def update ( self , * args , * * kwargs ) :
2011-09-20 12:37:45 +02:00
" Custom update "
super ( PackedDict , self ) . update ( * args , * * kwargs )
2012-02-24 23:22:38 +01:00
self . save ( )
2012-03-30 23:47:22 +02:00
2011-09-20 12:37:45 +02:00
class PackedList ( list ) :
"""
Attribute helper class .
2012-03-30 23:47:22 +02:00
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 12:37:45 +02:00
"""
2011-09-20 15:55:58 +02:00
def __init__ ( self , db_obj , * args , * * kwargs ) :
2011-09-20 12:37:45 +02:00
"""
2012-10-20 12:10:03 +02:00
sets up the packing list .
db_obj - the attribute object storing this list .
2012-02-24 23:22:38 +01:00
2012-10-20 12:10:03 +02:00
the ' parent ' property is set to ' init ' at creation ,
2012-02-24 23:22:38 +01:00
this stops the system from saving itself over and over
2012-10-20 12:10:03 +02:00
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-24 23:22:38 +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 ( 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 ) :
2012-10-20 12:10:03 +02:00
" relay save operation upwards in tree until we hit the root. "
2012-02-24 23:22:38 +01:00
if self . parent == ' init ' :
pass
elif self . parent :
2012-03-30 23:47:22 +02:00
self . parent . save ( )
2012-02-24 23:22:38 +01:00
else :
2012-03-30 23:47:22 +02:00
self . db_obj . value = self
def __setitem__ ( self , * args , * * kwargs ) :
2012-02-14 23:40:16 +01:00
" Custom setitem that stores changed list to database. "
2012-03-30 23:47:22 +02:00
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
2012-10-20 12:10:03 +02:00
class PackedSet ( set ) :
"""
A variant of Set that stores new updates to the databse .
"""
def __init__ ( self , db_obj , * args , * * kwargs ) :
"""
sets up the packing set .
db_obj - the attribute object storing this set
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 )
"""
self . db_obj = db_obj
self . parent = ' init '
super ( PackedSet , self ) . __init__ ( * args , * * kwargs )
def __str__ ( self ) :
return " { %s } " % " , " . join ( str ( val ) for val in self )
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
def add ( self , * args , * * kwargs ) :
" Add an element to the set "
super ( PackedSet , self ) . add ( * args , * * kwargs )
self . save ( )
def clear ( self , * args , * * kwargs ) :
" Remove all elements from this set "
super ( PackedSet , self ) . clear ( * args , * * kwargs )
self . save ( )
def difference_update ( self , * args , * * kwargs ) :
" Remove all elements of another set from this set. "
super ( PackedSet , self ) . difference_update ( * args , * * kwargs )
self . save ( )
def discard ( self , * args , * * kwargs ) :
" Remove an element from a set if it is a member. \n If not a member, do nothing. "
super ( PackedSet , self ) . discard ( * args , * * kwargs )
self . save ( )
def intersection_update ( self , * args , * * kwargs ) :
" Update a set with the intersection of itself and another. "
super ( PackedSet , self ) . intersection_update ( * args , * * kwargs )
self . save ( )
def pop ( self , * args , * * kwargs ) :
" Remove and return an arbitrary set element. \n Raises KeyError if the set is empty. "
super ( PackedSet , self ) . pop ( * args , * * kwargs )
self . save ( )
def remove ( self , * args , * * kwargs ) :
" Remove an element from a set; it must be a member. \n If the element is not a member, raise a KeyError. "
super ( PackedSet , self ) . remove ( * args , * * kwargs )
self . save ( )
def symmetric_difference_update ( self , * args , * * kwargs ) :
" Update a set with the symmetric difference of itself and another. "
super ( PackedSet , self ) . symmetric_difference_update ( * args , * * kwargs )
self . save ( )
def update ( self , * args , * * kwargs ) :
" Update a set with the union of itself and others. "
super ( PackedSet , self ) . update ( * args , * * kwargs )
self . save ( )
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
2012-03-30 23:47:22 +02:00
storing them directly . The added benefit is that we can add / remove
2010-08-29 18:46:58 +00:00
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 > }
2012-03-30 23:47:22 +02:00
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
2012-02-14 23:40:16 +01:00
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 )
2012-03-30 23:47:22 +02:00
# access through the value property
2012-09-28 09:08:43 +02:00
db_value = models . TextField ( ' value ' , blank = True , null = True )
2012-03-30 23:47:22 +02:00
# Lock storage
db_lock_storage = models . CharField ( ' locks ' , max_length = 512 , blank = True )
# references the object the attribute is linked to (this is set
2010-08-29 18:46:58 +00:00
# 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 )
2012-03-30 23:47:22 +02:00
# Database manager
2010-08-29 18:46:58 +00:00
objects = managers . AttributeManager ( )
2012-03-30 23:47:22 +02: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
2012-03-30 23:47:22 +02:00
2010-08-29 18:46:58 +00:00
class Meta :
" Define Django meta options "
2012-03-30 23:47:22 +02:00
abstract = True
verbose_name = " Evennia Attribute "
2012-02-14 23:40:16 +01:00
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
2012-03-30 23:47:22 +02:00
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
2010-08-29 18:46:58 +00:00
# is the object in question).
# key property (wraps db_key)
#@property
2012-03-31 13:06:29 +02:00
def __key_get ( self ) :
2012-03-30 23:47:22 +02:00
" Getter. Allows for value = self.key "
2012-03-31 13:06:29 +02:00
return _get_cache ( self , " key " )
2010-08-29 18:46:58 +00:00
#@key.setter
2012-03-31 13:06:29 +02:00
def __key_set ( self , value ) :
2010-08-29 18:46:58 +00:00
" Setter. Allows for self.key = value "
2012-03-31 13:06:29 +02:00
_set_cache ( self , " key " , value )
2010-08-29 18:46:58 +00:00
#@key.deleter
2012-03-31 13:06:29 +02:00
def __key_del ( self ) :
2010-08-29 18:46:58 +00:00
" Deleter. Allows for del self.key "
raise Exception ( " Cannot delete attribute key! " )
2012-03-31 13:06:29 +02:00
key = property ( __key_get , __key_set , __key_del )
2010-08-29 18:46:58 +00:00
# obj property (wraps db_obj)
#@property
2012-03-31 13:06:29 +02:00
def __obj_get ( self ) :
2010-08-29 18:46:58 +00:00
" Getter. Allows for value = self.obj "
2012-03-31 13:06:29 +02:00
return _get_cache ( self , " obj " )
2010-08-29 18:46:58 +00:00
#@obj.setter
2012-03-31 13:06:29 +02:00
def __obj_set ( self , value ) :
2010-08-29 18:46:58 +00:00
" Setter. Allows for self.obj = value "
2012-03-31 13:06:29 +02:00
_set_cache ( self , " obj " , value )
2010-08-29 18:46:58 +00:00
#@obj.deleter
2012-03-31 13:06:29 +02:00
def __obj_del ( self ) :
2010-08-29 18:46:58 +00:00
" Deleter. Allows for del self.obj "
self . db_obj = None
self . save ( )
2012-03-31 13:06:29 +02:00
_del_cache ( self , " obj " )
obj = property ( __obj_get , __obj_set , __obj_del )
2010-08-29 18:46:58 +00:00
# date_created property (wraps db_date_created)
#@property
2012-03-31 13:06:29 +02:00
def __date_created_get ( self ) :
2010-08-29 18:46:58 +00:00
" Getter. Allows for value = self.date_created "
2012-03-31 13:06:29 +02:00
return _get_cache ( self , " date_created " )
2010-08-29 18:46:58 +00:00
#@date_created.setter
2012-03-31 13:06:29 +02:00
def __date_created_set ( self , value ) :
2010-08-29 18:46:58 +00:00
" 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
2012-03-31 13:06:29 +02:00
def __date_created_del ( self ) :
2010-08-29 18:46:58 +00:00
" Deleter. Allows for del self.date_created "
raise Exception ( " Cannot delete date_created! " )
2012-03-31 13:06:29 +02:00
date_created = property ( __date_created_get , __date_created_set , __date_created_del )
2010-08-29 18:46:58 +00:00
# value property (wraps db_value)
#@property
2012-03-31 13:06:29 +02:00
def __value_get ( self ) :
2010-08-29 18:46:58 +00:00
"""
2012-02-14 23:40:16 +01:00
Getter . Allows for value = self . value . Reads from cache if possible .
2012-03-30 23:47:22 +02:00
"""
2012-02-14 23:40:16 +01:00
if self . no_cache :
2012-03-30 23:47:22 +02:00
# re-create data from database and cache it
2012-02-14 23:40:16 +01:00
try :
2012-03-31 13:06:29 +02:00
value = self . __from_attr ( _PLOADS ( to_str ( self . db_value ) ) )
2012-02-14 23:40:16 +01:00
except pickle . UnpicklingError :
2012-03-30 23:47:22 +02:00
value = self . db_value
2012-02-14 23:40:16 +01:00
self . cached_value = value
2012-03-30 23:47:22 +02:00
self . no_cache = False
2012-02-14 23:40:16 +01:00
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
2012-03-31 13:06:29 +02:00
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 .
2012-03-30 23:47:22 +02:00
"""
2012-03-31 13:06:29 +02:00
new_value = self . __to_attr ( new_value )
self . cached_value = self . __from_attr ( new_value )
2012-02-14 23:40:16 +01:00
self . no_cache = False
2012-03-31 13:06:29 +02:00
self . db_value = to_unicode ( _PDUMPS ( to_str ( new_value ) ) )
2010-08-29 18:46:58 +00:00
self . save ( )
2012-10-14 19:29:56 +02:00
2010-08-29 18:46:58 +00:00
#@value.deleter
2012-03-31 13:06:29 +02:00
def __value_del ( self ) :
2010-08-29 18:46:58 +00:00
" Deleter. Allows for del attr.value. This removes the entire attribute. "
self . delete ( )
2012-03-31 13:06:29 +02:00
value = property ( __value_get , __value_set , __value_del )
2010-08-29 18:46:58 +00:00
2011-03-15 16:08:32 +00:00
# lock_storage property (wraps db_lock_storage)
2012-03-30 23:47:22 +02:00
#@property
2012-03-31 13:06:29 +02:00
def __lock_storage_get ( self ) :
2011-03-15 16:08:32 +00:00
" Getter. Allows for value = self.lock_storage "
2012-03-31 13:06:29 +02:00
return _get_cache ( self , " lock_storage " )
2011-03-15 16:08:32 +00:00
#@lock_storage.setter
2012-03-31 13:06:29 +02:00
def __lock_storage_set ( self , value ) :
2011-03-15 16:08:32 +00:00
""" Saves the lock_storage. This is usually not called directly, but through self.lock() """
self . db_lock_storage = value
self . save ( )
#@lock_storage.deleter
2012-03-31 13:06:29 +02:00
def __lock_storage_del ( self ) :
2011-03-15 16:08:32 +00:00
" 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 )
2012-03-31 13:06:29 +02:00
lock_storage = property ( __lock_storage_get , __lock_storage_set , __lock_storage_del )
2012-03-30 23:47:22 +02: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 ) )
2012-03-30 23:47:22 +02:00
2010-08-29 18:46:58 +00:00
def __unicode__ ( self ) :
return u " %s ( %s ) " % ( self . key , self . id )
2012-03-30 23:47:22 +02:00
# operators on various data
2012-03-31 13:06:29 +02:00
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-03-30 23:47:22 +02:00
data storage format :
2012-02-14 23:40:16 +01:00
( simple | dbobj | iter , < data > )
2012-03-30 23:47:22 +02:00
where
2012-02-14 23:40:16 +01:00
simple - a single non - db object , like a string or number
dbobj - a single dbobj
iter - any iterable object - will be looped over recursively
2012-03-30 23:47:22 +02:00
to convert dbobj - > id .
2012-02-14 23:40:16 +01:00
"""
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
2012-03-30 23:47:22 +02:00
return item
2012-03-31 13:06:29 +02:00
elif hasattr ( item , " id " ) and hasattr ( item , " _db_model_name " ) and hasattr ( item , " db_key " ) :
db_model_name = item . _db_model_name # don't use _GA here, could be typeclass
2012-02-14 23:40:16 +01:00
if db_model_name == " typeclass " :
2012-03-31 13:06:29 +02:00
db_model_name = _GA ( item . dbobj , " _db_model_name " )
2012-02-14 23:40:16 +01:00
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 ( ) )
2012-10-20 12:10:03 +02:00
elif dtype in ( set , PackedSet ) :
return set ( iter_db2id ( val ) for val in item )
2012-02-14 23:40:16 +01:00
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 )
2012-03-31 13:06:29 +02:00
elif hasattr ( data , " id " ) and hasattr ( data , " _db_model_name " ) and hasattr ( data , ' db_key ' ) :
2012-02-14 23:40:16 +01:00
# all django models (objectdb,scriptdb,playerdb,channel,msg,typeclass)
2012-03-31 13:06:29 +02:00
# have the protected property _db_model_name hardcoded on themselves for speed.
db_model_name = data . _db_model_name # don't use _GA here, could be typeclass
2012-02-14 23:40:16 +01:00
if db_model_name == " typeclass " :
# typeclass cannot help us, we want the actual child object model name
2012-03-31 13:06:29 +02:00
db_model_name = _GA ( data . dbobj , " _db_model_name " )
2012-03-30 23:47:22 +02:00
return ( " dbobj " , PackedDBobject ( data . id , db_model_name , data . db_key ) )
elif hasattr ( data , " __iter__ " ) :
2012-02-14 23:40:16 +01:00
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 )
2012-03-30 23:47:22 +02:00
2012-03-31 13:06:29 +02:00
def __from_attr ( self , datatuple ) :
2012-02-14 23:40:16 +01:00
"""
Retrieve data from a previously stored attribute . This
2012-03-30 23:47:22 +02:00
is always a dict with keys type and data .
2012-02-14 23:40:16 +01:00
2012-03-30 23:47:22 +02:00
datatuple comes from the database storage and has
the following format :
2012-02-14 23:40:16 +01:00
( simple | dbobj | iter , < data > )
where
simple - a single non - db object , like a string . is returned as - is .
2012-03-30 23:47:22 +02:00
dbobj - a single dbobj - id . This id is retrieved back from the database .
2012-02-14 23:40:16 +01:00
iter - an iterable . This is traversed iteratively , converting all found
2012-03-30 23:47:22 +02:00
dbobj - ids back to objects . Also , all lists and dictionaries are
returned as their PackedList / PackedDict counterparts in order to
2012-02-14 23:40:16 +01:00
allow in - place assignment such as obj . db . mylist [ 3 ] = val . Mylist
2012-03-30 23:47:22 +02:00
is then a PackedList that saves the data on the fly .
2012-02-14 23:40:16 +01:00
"""
2012-03-30 23:47:22 +02:00
# nested functions
2012-02-14 23:40:16 +01:00
def id2db ( data ) :
"""
Convert db - stored dbref back to object
"""
2012-03-31 13:06:29 +02: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.
2012-03-30 23:47:22 +02:00
return None
2012-02-14 23:40:16 +01:00
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
2012-03-30 23:47:22 +02:00
stored in the database are however always normal python primitives .
2012-02-14 23:40:16 +01:00
"""
dtype = type ( item )
if dtype in ( basestring , int , float ) : # check the most common types first, for speed
2012-03-30 23:47:22 +02:00
return item
2012-02-14 23:40:16 +01:00
elif dtype == PackedDBobject :
2012-03-30 23:47:22 +02:00
return id2db ( item )
elif dtype == tuple :
2012-02-14 23:40:16 +01:00
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-10-20 12:10:03 +02:00
elif dtype in ( set , PackedSet ) :
pset = PackedSet ( self )
pset . update ( set ( iter_id2db ( val ) for val in item ) )
return pset
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-03-30 23:47:22 +02:00
else :
return item
2012-02-14 23:40:16 +01:00
typ , data = datatuple
2012-03-30 23:47:22 +02:00
if typ == ' simple ' :
2012-02-14 23:40:16 +01:00
# single non-db objects
return data
2012-03-30 23:47:22 +02:00
elif typ == ' dbobj ' :
# a single stored dbobj
2012-02-14 23:40:16 +01:00
return id2db ( data )
2012-03-30 23:47:22 +02:00
elif typ == ' iter ' :
2012-02-14 23:40:16 +01:00
# all types of iterables
return iter_id2db ( data )
2012-03-30 23:47:22 +02: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
2012-03-30 23:47:22 +02:00
"""
2011-03-15 16:08:32 +00:00
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 ) :
"""
2012-03-30 23:47:22 +02:00
This model holds whichever alternate names this object
2011-04-23 11:54:08 +00:00
has for OTHER objects , but also for arbitrary strings ,
channels , players etc . Setting a nick does not affect
2012-03-30 23:47:22 +02:00
the nicknamed object at all ( as opposed to Aliases above ) ,
2011-04-23 11:54:08 +00:00
and only this object will be able to refer to the nicknamed
2012-03-30 23:47:22 +02:00
object by the given nick .
2011-04-23 11:54:08 +00:00
2012-03-30 23:47:22 +02:00
The default nick types used by Evennia are :
2011-04-23 11:54:08 +00:00
inputline ( default ) - match against all input
player - match against player searches
2012-03-30 23:47:22 +02:00
obj - match against object searches
2011-04-23 11:54:08 +00:00
channel - used to store own names for channels
"""
2012-03-30 23:47:22 +02:00
db_nick = models . CharField ( ' nickname ' , max_length = 255 , db_index = True , help_text = ' the alias ' )
2011-10-02 01:21:03 +02:00
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 "
2012-03-30 23:47:22 +02:00
abstract = True
2011-04-23 11:54:08 +00:00
verbose_name = " Nickname "
unique_together = ( " db_nick " , " db_type " , " db_obj " )
class TypeNickHandler ( object ) :
"""
Handles nick access and setting . Accessed through ObjectDB . nicks
2012-03-30 23:47:22 +02:00
"""
2011-04-23 11:54:08 +00:00
2012-03-30 23:47:22 +02:00
NickClass = TypeNick
2011-04-23 11:54:08 +00:00
def __init__ ( self , obj ) :
2012-03-29 19:42:08 +02:00
"""
This handler allows for accessing and setting nicks -
on - the - fly replacements for various text input passing through
this object ( most often a Character )
2012-03-30 23:47:22 +02:00
The default nick types used by Evennia are :
2012-03-29 19:42:08 +02:00
inputline ( default ) - match against all input
player - match against player searches
2012-03-30 23:47:22 +02:00
obj - match against object searches
2012-03-29 19:42:08 +02:00
channel - used to store own names for channels
You can define other nicktypes by using the add ( ) method of
this handler and set nick_type to whatever you want . It ' s then
up to you to somehow make use of this nick_type in your game
( such as for a " recog " system ) .
"""
2011-04-23 11:54:08 +00:00
self . obj = obj
def add ( self , nick , realname , nick_type = " inputline " ) :
2012-03-29 19:42:08 +02:00
"""
2012-03-30 23:47:22 +02:00
Assign a new nick for realname .
nick_types used by Evennia are
2012-03-29 19:42:08 +02:00
' inputline ' , ' player ' , ' obj ' and ' channel '
"""
2011-04-23 11:54:08 +00:00
if not nick or not nick . strip ( ) :
2012-03-30 23:47:22 +02:00
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 )
2011-04-23 11:54:08 +00:00
if query . count ( ) :
old_nick = query [ 0 ]
old_nick . db_real = real
old_nick . save ( )
2012-03-30 23:47:22 +02:00
else :
2011-04-23 11:54:08 +00:00
new_nick = self . NickClass ( db_nick = nick , db_real = real , db_type = nick_type , db_obj = self . obj )
2012-03-30 23:47:22 +02:00
new_nick . save ( )
def delete ( self , nick , nick_type = " inputline " ) :
2012-03-29 19:42:08 +02:00
" Removes a previously stored nick "
2012-03-30 23:47:22 +02:00
nick = nick . strip ( )
query = self . NickClass . objects . filter ( db_obj = self . obj , db_nick__iexact = nick , db_type__iexact = nick_type )
2011-04-23 11:54:08 +00:00
if query . count ( ) :
# remove the found nick(s)
2012-03-30 23:47:22 +02:00
query . delete ( )
2012-03-29 19:42:08 +02:00
def get ( self , nick = None , nick_type = " inputline " , obj = None ) :
2012-03-31 13:06:29 +02:00
"""
Retrieves a given nick ( with a specified nick_type ) on an object . If no nick is given , returns a list
2012-03-30 23:47:22 +02:00
of all nicks on the object , or the empty list .
2012-03-31 13:06:29 +02:00
Defaults to searching the current object .
"""
2012-03-29 19:42:08 +02:00
if not obj :
# defaults to the current object
obj = self . obj
2011-04-23 11:54:08 +00:00
if nick :
2012-03-30 23:47:22 +02:00
query = self . NickClass . objects . filter ( db_obj = obj , db_nick__iexact = nick , db_type__iexact = nick_type )
2011-04-23 11:54:08 +00:00
query = query . values_list ( " db_real " , flat = True )
if query . count ( ) :
return query [ 0 ]
else :
return nick
else :
2012-03-29 19:42:08 +02:00
return self . NickClass . objects . filter ( db_obj = obj )
def has ( self , nick , nick_type = " inputline " , obj = None ) :
"""
Returns true / false if this nick and nick_type is defined on the given
2012-03-30 23:47:22 +02:00
object or not . If no obj is given , default to the current object the
handler is defined on .
2012-03-29 19:42:08 +02:00
"""
if not obj :
obj = self . obj
return self . NickClass . objects . filter ( db_obj = obj , db_nick__iexact = nick , db_type__iexact = nick_type ) . count ( )
2011-04-23 11:54:08 +00:00
2010-08-29 18:46:58 +00:00
#------------------------------------------------------------
#
2012-03-30 23:47:22 +02:00
# Typed Objects
2010-08-29 18:46:58 +00:00
#
2012-03-30 23:47:22 +02:00
#------------------------------------------------------------
2010-08-29 18:46:58 +00:00
2012-04-28 00:37:36 +02:00
2010-08-29 18:46:58 +00:00
class TypedObject ( SharedMemoryModel ) :
"""
Abstract Django model .
2012-03-30 23:47:22 +02:00
2010-08-29 18:46:58 +00:00
This is the basis for a typed object . It also contains all the
2012-03-30 23:47:22 +02:00
mechanics for managing connected attributes .
2010-08-29 18:46:58 +00:00
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
2012-03-30 23:47:22 +02:00
permissions - perm strings
dbref - #id of object
2010-08-29 18:46:58 +00:00
db - persistent attribute storage
2012-03-30 23:47:22 +02:00
ndb - non - persistent attribute storage
2010-08-29 18:46:58 +00:00
2012-03-30 23:47:22 +02:00
"""
2010-08-29 18:46:58 +00:00
2012-03-30 23:47:22 +02:00
#
2010-08-29 18:46:58 +00:00
# TypedObject Database Model setup
#
#
# These databse fields are all set using their corresponding properties,
# named same as the field, but withtou the db_* prefix.
2012-03-30 23:47:22 +02:00
2010-08-29 18:46:58 +00:00
# Main identifier of the object, for searching. Can also
2012-03-30 23:47:22 +02:00
# 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. " )
2012-03-30 23:47:22 +02:00
# Lock storage
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 ( )
2012-03-30 23:47:22 +02:00
# object cache and flags
_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 )
2012-03-30 23:47:22 +02:00
2010-08-29 18:46:58 +00:00
class Meta :
"""
Django setup info .
"""
2012-03-30 23:47:22 +02:00
abstract = True
2010-08-29 18:46:58 +00:00
verbose_name = " Evennia Database Object "
ordering = [ ' -db_date_created ' , ' id ' , ' db_typeclass_path ' , ' db_key ' ]
2012-03-30 23:47:22 +02:00
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
2012-03-30 23:47:22 +02:00
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
2010-08-29 18:46:58 +00:00
# is the object in question).
# key property (wraps db_key)
#@property
2012-03-31 13:06:29 +02:00
def __key_get ( self ) :
2010-08-29 18:46:58 +00:00
" Getter. Allows for value = self.key "
2012-03-31 13:06:29 +02:00
return _get_cache ( self , " key " )
2010-08-29 18:46:58 +00:00
#@key.setter
2012-03-31 13:06:29 +02:00
def __key_set ( self , value ) :
2010-08-29 18:46:58 +00:00
" Setter. Allows for self.key = value "
2012-03-31 13:06:29 +02:00
_set_cache ( self , " key " , value )
2010-08-29 18:46:58 +00:00
#@key.deleter
2012-03-31 13:06:29 +02:00
def __key_del ( self ) :
2010-08-29 18:46:58 +00:00
" Deleter. Allows for del self.key "
raise Exception ( " Cannot delete objectdb key! " )
2012-03-31 13:06:29 +02:00
key = property ( __key_get , __key_set , __key_del )
2010-08-29 18:46:58 +00:00
# name property (wraps db_key too - alias to self.key)
#@property
2012-03-31 13:06:29 +02:00
def __name_get ( self ) :
2010-08-29 18:46:58 +00:00
" Getter. Allows for value = self.name "
2012-03-31 13:06:29 +02:00
return _get_cache ( self , " key " )
2010-08-29 18:46:58 +00:00
#@name.setter
2012-03-31 13:06:29 +02:00
def __name_set ( self , value ) :
2010-08-29 18:46:58 +00:00
" Setter. Allows for self.name = value "
2012-03-31 13:06:29 +02:00
_set_cache ( self , " key " , value )
2010-08-29 18:46:58 +00:00
#@name.deleter
2012-03-31 13:06:29 +02:00
def __name_del ( self ) :
2010-08-29 18:46:58 +00:00
" Deleter. Allows for del self.name "
raise Exception ( " Cannot delete name! " )
2012-03-31 13:06:29 +02:00
name = property ( __name_get , __name_set , __name_del )
2010-08-29 18:46:58 +00:00
# typeclass_path property
#@property
2012-03-31 13:06:29 +02:00
def __typeclass_path_get ( self ) :
2012-03-30 23:47:22 +02:00
" Getter. Allows for value = self.typeclass_path "
2012-03-31 13:06:29 +02:00
return _get_cache ( self , " typeclass_path " )
2010-08-29 18:46:58 +00:00
#@typeclass_path.setter
2012-03-31 13:06:29 +02:00
def __typeclass_path_set ( self , value ) :
2010-08-29 18:46:58 +00:00
" Setter. Allows for self.typeclass_path = value "
2012-03-31 13:06:29 +02:00
_set_cache ( self , " typeclass_path " , value )
2010-08-29 18:46:58 +00:00
#@typeclass_path.deleter
2012-03-31 13:06:29 +02:00
def __typeclass_path_del ( self ) :
2010-08-29 18:46:58 +00:00
" 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-03-31 13:06:29 +02:00
_del_cache ( self , " typeclass_path " )
typeclass_path = property ( __typeclass_path_get , __typeclass_path_set , __typeclass_path_del )
2010-08-29 18:46:58 +00:00
# date_created property
#@property
2012-03-31 13:06:29 +02:00
def __date_created_get ( self ) :
2010-08-29 18:46:58 +00:00
" Getter. Allows for value = self.date_created "
2012-03-31 13:06:29 +02:00
return _get_cache ( self , " date_created " )
2010-08-29 18:46:58 +00:00
#@date_created.setter
2012-03-31 13:06:29 +02:00
def __date_created_set ( self , value ) :
2010-08-29 18:46:58 +00:00
" 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
2012-03-31 13:06:29 +02:00
def __date_created_del ( self ) :
2010-08-29 18:46:58 +00:00
" Deleter. Allows for del self.date_created "
raise Exception ( " Cannot delete date_created! " )
2012-03-31 13:06:29 +02:00
date_created = property ( __date_created_get , __date_created_set , __date_created_del )
2010-08-29 18:46:58 +00:00
# permissions property
#@property
2012-03-31 13:06:29 +02:00
def __permissions_get ( self ) :
2010-08-29 18:46:58 +00:00
" Getter. Allows for value = self.name. Returns a list of permissions. "
2012-03-31 13:06:29 +02:00
perms = _get_cache ( self , " permissions " )
2012-02-25 23:37:50 +01:00
if perms :
return [ perm . strip ( ) for perm in perms . split ( ' , ' ) ]
2010-08-29 18:46:58 +00:00
return [ ]
#@permissions.setter
2012-03-31 13:06:29 +02:00
def __permissions_set ( self , value ) :
2012-03-30 23:47:22 +02:00
" Setter. Allows for self.name = value. Stores as a comma-separated string. "
2012-02-26 01:10:20 +01:00
value = " , " . join ( [ utils . to_unicode ( val ) . strip ( ) for val in make_iter ( value ) ] )
2012-03-31 13:06:29 +02:00
_set_cache ( self , " permissions " , value )
2010-08-29 18:46:58 +00:00
#@permissions.deleter
2012-03-31 13:06:29 +02:00
def __permissions_del ( self ) :
2010-08-29 18:46:58 +00:00
" Deleter. Allows for del self.name "
self . db_permissions = " "
self . save ( )
2012-03-31 13:06:29 +02:00
_del_cache ( self , " permissions " )
permissions = property ( __permissions_get , __permissions_set , __permissions_del )
2010-08-29 18:46:58 +00:00
2011-03-15 16:08:32 +00:00
# lock_storage property (wraps db_lock_storage)
2012-03-30 23:47:22 +02:00
#@property
2012-03-31 13:06:29 +02:00
def __lock_storage_get ( self ) :
2011-03-15 16:08:32 +00:00
" Getter. Allows for value = self.lock_storage "
2012-03-31 13:06:29 +02:00
return _get_cache ( self , " lock_storage " )
2011-03-15 16:08:32 +00:00
#@lock_storage.setter
2012-03-31 13:06:29 +02:00
def __lock_storage_set ( self , value ) :
2011-03-15 16:08:32 +00:00
""" Saves the lock_storagetodate. This is usually not called directly, but through self.lock() """
2012-03-31 13:06:29 +02:00
_set_cache ( self , " lock_storage " , value )
2011-03-15 16:08:32 +00:00
#@lock_storage.deleter
2012-03-31 13:06:29 +02:00
def __lock_storage_del ( self ) :
2011-03-15 16:08:32 +00:00
" 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 )
2012-03-31 13:06:29 +02:00
lock_storage = property ( __lock_storage_get , __lock_storage_set , __lock_storage_del )
2011-03-15 16:08:32 +00:00
2010-08-29 18:46:58 +00:00
#
#
2012-03-30 23:47:22 +02:00
# TypedObject main class methods and properties
2010-08-29 18:46:58 +00:00
#
#
2012-02-25 23:37:50 +01:00
# these are identifiers for fast Attribute access and caching
2012-03-31 13:06:29 +02:00
_typeclass_paths = settings . OBJECT_TYPECLASS_PATHS
_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
2012-03-30 23:47:22 +02:00
def __eq__ ( self , other ) :
2012-04-26 17:47:25 +02:00
return other and hasattr ( other , ' dbid ' ) and self . dbid == other . dbid
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-03-31 13:06:29 +02: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-08-22 22:34:43 +02:00
return _GA ( _GA ( self , ' typeclass ' ) , propname )
2012-08-19 11:45:13 +02:00
2012-09-18 01:03:35 +02:00
def _hasattr ( self , obj , attrname ) :
"""
Loop - safe version of hasattr , to avoid running a lookup that
will be rerouted up the typeclass . Returns True / False .
"""
try :
_GA ( obj , attrname )
return True
except AttributeError :
return False
2012-04-26 17:47:25 +02:00
#@property
_dbid_cache = None
def __dbid_get ( self ) :
"""
Caches and returns the unique id of the object .
Use this instead of self . id , which is not cached .
"""
dbid = _GA ( self , " _dbid_cache " )
if not dbid :
dbid = _GA ( self , " id " )
_SA ( self , " _dbid_cache " , dbid )
return dbid
def __dbid_set ( self , value ) :
raise Exception ( " dbid cannot be set! " )
def __dbid_del ( self ) :
raise Exception ( " dbid cannot be deleted! " )
dbid = property ( __dbid_get , __dbid_set , __dbid_del )
2010-08-29 18:46:58 +00:00
#@property
2012-03-31 13:06:29 +02:00
def __dbref_get ( self ) :
2010-08-29 18:46:58 +00:00
"""
2012-04-26 17:47:25 +02:00
Returns the object ' s dbref on the form #NN.
2010-08-29 18:46:58 +00:00
"""
2012-04-26 17:47:25 +02:00
return " # %s " % _GA ( self , " _TypedObject__dbid_get " ) ( )
def __dbref_set ( self ) :
raise Exception ( " dbref cannot be set! " )
def __dbref_del ( self ) :
raise Exception ( " dbref cannot be deleted! " )
dbref = property ( __dbref_get , __dbref_set , __dbref_del )
2010-08-29 18:46:58 +00:00
2012-04-28 14:47:11 +02:00
#@property
_hashid_cache = None
def __hashid_get ( self ) :
"""
Returns a per - class unique that combines the object ' s
class name with its idnum . This makes this id unique also
between different typeclassed entities such as scripts and
objects ( which may still have the same id ) .
Primarily used by Attribute caching system .
"""
hashid = _GA ( self , " _hashid_cache " )
if not hashid :
hashid = " %s <# %s > " % ( _GA ( self , " __class__ " ) , _GA ( self , " dbid " ) )
_SA ( self , " _hashid_cache " , hashid )
return hashid
def __hashid_set ( self ) :
raise Exception ( " hashid cannot be set! " )
def __hashid_del ( self ) :
raise Exception ( " hashid cannot be deleted! " )
hashid = property ( __hashid_get , __hashid_set , __hashid_del )
2010-08-29 18:46:58 +00:00
# typeclass property
#@property
2012-03-31 13:06:29 +02:00
def __typeclass_get ( self ) :
2010-08-29 18:46:58 +00:00
"""
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-03-31 13:06:29 +02:00
Note : The liberal use of _GA and __setattr__ ( instead
2012-03-30 23:47:22 +02:00
of normal dot notation ) is due to optimization : it avoids calling
the custom self . __getattribute__ more than necessary .
"""
2011-08-06 18:15:04 +00:00
2012-03-31 13:06:29 +02:00
path = _GA ( self , " typeclass_path " )
typeclass = _GA ( self , " _cached_typeclass " )
2011-08-06 18:15:04 +00:00
try :
2012-03-31 13:06:29 +02: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-03-31 13:06:29 +02:00
return _GA ( self , " _get_default_typeclass " ) ( cache = True , silent = True , save = True )
2012-03-30 23:47:22 +02:00
else :
2011-05-13 22:26:08 +00:00
# handle loading/importing of typeclasses, searching all paths.
2012-03-31 13:06:29 +02: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
2012-03-30 23:47:22 +02:00
# entities).
2012-03-31 13:06:29 +02:00
typeclass_paths = [ path ] + [ " %s . %s " % ( prefix , path ) for prefix in _GA ( self , ' _typeclass_paths ' ) ]
2012-03-30 23:47:22 +02:00
for tpath in typeclass_paths :
2011-08-06 18:15:04 +00:00
# try to import and analyze the result
2012-03-31 13:06:29 +02:00
typeclass = _GA ( self , " _path_import " ) ( tpath )
2011-05-13 22:26:08 +00:00
if callable ( typeclass ) :
2012-03-30 23:47:22 +02:00
# we succeeded to import. Cache and return.
2012-03-31 13:06:29 +02:00
_SA ( self , ' db_typeclass_path ' , tpath )
_GA ( self , ' save ' ) ( )
_SA ( self , " _cached_db_typeclass_path " , tpath )
2011-10-01 22:00:22 +02:00
typeclass = typeclass ( self )
2012-03-31 13:06:29 +02: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. "
2012-03-30 23:47:22 +02:00
else :
errstring + = " \n %s " % typeclass # this will hold a growing error message.
2011-08-06 18:15:04 +00:00
# If we reach this point we couldn't import any typeclasses. Return default. It's up to the calling
2012-03-30 23:47:22 +02:00
# method to use e.g. self.is_typeclass() to detect that the result is not the one asked for.
2012-03-31 13:06:29 +02:00
_GA ( self , " _display_errmsg " ) ( errstring )
2012-04-21 16:15:37 +02:00
_SA ( self , " typeclass_lasterrmsg " , errstring )
2012-03-31 13:06:29 +02:00
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
2012-03-31 13:06:29 +02: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
2012-03-30 23:47:22 +02:00
# typeclass property
2012-03-31 13:06:29 +02:00
typeclass = property ( __typeclass_get , fdel = __typeclass_del )
2012-03-30 23:47:22 +02:00
2012-04-21 16:15:37 +02:00
# the last error string will be stored here for accessing methods to access.
# It is set by _display_errmsg, which will print to log if error happens
# during server startup.
typeclass_last_errmsg = " "
2010-08-29 18:46:58 +00:00
def _path_import ( self , path ) :
"""
Import a class from a python path of the
form src . objects . object . Object
"""
errstring = " "
if not path :
2012-03-30 23:47:22 +02:00
# this needs not be bad, it just means
2010-08-29 18:46:58 +00:00
# we should use defaults.
2012-03-30 23:47:22 +02:00
return None
try :
2010-08-29 18:46:58 +00:00
modpath , class_name = path . rsplit ( ' . ' , 1 )
2012-04-21 16:15:37 +02:00
module = __import__ ( modpath , fromlist = [ " none " ] )
2011-06-24 20:12:59 +00:00
return module . __dict__ [ class_name ]
2012-03-30 23:47:22 +02:00
except ImportError :
trc = sys . exc_traceback
2011-06-24 20:12:59 +00:00
if not trc . tb_next :
# we separate between not finding the module, and finding a buggy one.
2012-04-21 16:15:37 +02:00
errstring = " Typeclass not found trying path ' %s ' . " % path
2011-06-24 20:12:59 +00:00
else :
# a bug in the module is reported normally.
2012-03-30 23:47:22 +02:00
trc = traceback . format_exc ( )
2012-04-21 16:15:37 +02:00
errstring = " \n %s Error importing ' %s ' . " % ( trc , path )
except ( ValueError , TypeError ) :
errstring = " Malformed typeclass path ' %s ' . " % path
2010-08-29 18:46:58 +00:00
except KeyError :
2012-03-30 23:47:22 +02:00
errstring = " No class ' %s ' was found in module ' %s ' . "
2010-08-29 18:46:58 +00:00
errstring = errstring % ( class_name , modpath )
except Exception :
2012-03-30 23:47:22 +02: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
2012-03-30 23:47:22 +02:00
def _display_errmsg ( self , message ) :
2011-08-06 18:15:04 +00:00
"""
Helper function to display error .
"""
2012-04-21 16:15:37 +02:00
if ServerConfig . objects . conf ( " server_starting_mode " ) :
print message . strip ( )
else :
_SA ( self , " typeclass_last_errmsg " , message . strip ( ) )
return
#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
# cmessage = "\n".join(["[%s]: %s" % (cname, line) for line in message.split('\n') if line])
# cmessage = cmessage.strip()
# infochan.msg(cmessage)
# 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)
#except Exception:
# if ServerConfig.objects.conf("server_starting_mode"):
# print cmessage
# else:
# logger.log_trace(cmessage)
2012-03-30 23:47:22 +02:00
2012-03-31 13:06:29 +02:00
def _get_default_typeclass ( self , cache = False , silent = False , save = False ) :
2011-08-06 18:15:04 +00:00
"""
2012-03-30 23:47:22 +02:00
This is called when a typeclass fails to
load for whatever reason .
Overload this in different entities .
2011-08-06 18:15:04 +00:00
Default operation is to load a default typeclass .
"""
2012-03-31 13:06:29 +02:00
defpath = _GA ( self , " _default_typeclass_path " )
typeclass = _GA ( self , " _path_import " ) ( defpath )
2011-08-06 18:15:04 +00:00
# if not silent:
2012-03-30 23:47:22 +02:00
# #errstring = "\n\nUsing Default class '%s'." % defpath
2012-03-31 13:06:29 +02: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.
2012-03-30 23:47:22 +02:00
# fall back to hardcoded core class which is wrong for e.g. scripts/players etc.
2011-08-06 18:15:04 +00:00
failpath = defpath
defpath = " src.objects.objects.Object "
2012-03-31 13:06:29 +02: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
2012-03-30 23:47:22 +02:00
errstring + = " \n Using Evennia ' s default class ' %s ' . " % defpath
2012-03-31 13:06:29 +02: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-03-31 13:06:29 +02:00
_SA ( self , ' db_typeclass_path ' , defpath )
_GA ( self , ' save ' ) ( )
2011-08-06 18:15:04 +00:00
if cache :
2012-03-31 13:06:29 +02:00
_SA ( self , " _cached_db_typeclass_path " , defpath )
2011-10-01 22:00:22 +02:00
2012-03-31 13:06:29 +02:00
_SA ( self , " _cached_typeclass " , typeclass )
2012-03-30 23:47:22 +02:00
try :
2011-10-01 22:00:22 +02:00
typeclass . at_init ( )
except Exception :
logger . log_trace ( )
2012-03-30 23:47:22 +02:00
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
2012-04-21 16:15:37 +02:00
the given typeclass . This operates on the actually
loaded typeclass ( this is important since a failing
typeclass may instead have its default currently loaded )
2012-03-30 23:47:22 +02:00
2011-08-06 18:15:04 +00:00
typeclass - can be a class object or the
2012-03-30 23:47:22 +02:00
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 .
2012-03-30 23:47:22 +02:00
"""
2011-08-06 18:15:04 +00:00
try :
2012-03-31 13:06:29 +02:00
typeclass = _GA ( typeclass , " path " )
2011-08-06 18:15:04 +00:00
except AttributeError :
2012-03-30 23:47:22 +02:00
pass
2012-03-31 13:06:29 +02: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-04-21 16:15:37 +02:00
current_path = _GA ( self . typeclass , " path " ) #"_GA(self, "_cached_db_typeclass_path")
2012-02-25 23:37:50 +01:00
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 ( )
2012-03-31 13:06:29 +02:00
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
2012-03-30 23:47:22 +02:00
#
2010-08-29 18:46:58 +00:00
#
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
2012-03-30 23:47:22 +02:00
object entirely ( while retaining this object ' s type), use
2010-08-29 18:46:58 +00:00
self . player . swap_object ( ) .
2012-03-30 23:47:22 +02:00
Note that this might be an error prone operation if the
2010-08-29 18:46:58 +00:00
old / new typeclass was heavily customized - your code
2012-03-30 23:47:22 +02:00
might expect one and not the other , so be careful to
2010-08-29 18:46:58 +00:00
bug test your code if using this feature ! Often its easiest
to create a new object and just swap the player over to
2012-03-30 23:47:22 +02:00
that one instead .
2010-08-29 18:46:58 +00:00
2012-03-30 23:47:22 +02:00
Arguments :
new_typeclass ( path / classobj ) - type to switch to
2010-08-29 18:46:58 +00:00
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
2012-03-30 23:47:22 +02:00
will be preserved .
Returns :
2012-03-25 16:35:22 +02:00
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
2012-03-30 23:47:22 +02:00
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-31 13:06:29 +02:00
_DA ( self , " _cached_db_typeclass_path " )
_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-03-31 13:06:29 +02:00
_SA ( self , " typeclass_path " , old_typeclass_path )
_SA ( self , " _cached_typeclass " , None )
2011-10-03 23:53:23 +02:00
return False
2012-03-30 23:47:22 +02:00
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 ..."
for attr in self . get_all_attributes ( ) :
attr . delete ( )
2012-04-21 16:15:37 +02:00
for nattr in self . ndb . all :
2010-08-29 18:46:58 +00:00
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 ( )
2012-03-30 23:47:22 +02:00
return True
2010-08-29 18:46:58 +00:00
#
2012-03-30 23:47:22 +02:00
# Attribute handler methods
2010-08-29 18:46:58 +00:00
#
2012-03-30 23:47:22 +02:00
#
# Fully persistent attributes. You usually access these
# through the obj.db.attrname method.
# Helper methods for persistent attributes
2010-08-29 18:46:58 +00:00
def has_attribute ( self , attribute_name ) :
"""
See if we have an attribute set on the object .
2012-03-30 23:47:22 +02:00
2010-08-29 18:46:58 +00:00
attribute_name : ( str ) The attribute ' s name.
2012-03-30 23:47:22 +02:00
"""
2012-04-28 14:47:11 +02:00
if attribute_name not in _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] :
2012-04-26 21:39:16 +02:00
attrib_obj = _GA ( self , " _attribute_class " ) . objects . filter ( db_obj = self ) . filter (
db_key__iexact = attribute_name )
if attrib_obj :
2012-04-28 14:47:11 +02:00
_ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] [ attribute_name ] = attrib_obj [ 0 ]
2012-04-26 21:39:16 +02:00
else :
return False
return True
2012-03-30 23:47:22 +02:00
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 .
2012-03-30 23:47:22 +02:00
2010-08-29 18:46:58 +00:00
attribute_name : ( str ) The attribute ' s name.
new_value : ( python obj ) The value to set the attribute to . If this is not
2012-03-30 23:47:22 +02:00
a str , the object will be stored as a pickle .
2010-08-29 18:46:58 +00:00
"""
2012-04-28 14:47:11 +02:00
attrib_obj = _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] . get ( " attribute_name " )
2012-04-26 21:39:16 +02:00
if not attrib_obj :
attrclass = _GA ( self , " _attribute_class " )
# check if attribute already exists.
2012-02-05 21:04:10 +01:00
attrib_obj = attrclass . objects . filter (
2012-04-26 21:39:16 +02:00
db_obj = self ) . filter ( db_key__iexact = attribute_name )
if attrib_obj :
# use old attribute
attrib_obj = attrib_obj [ 0 ]
2012-04-27 00:03:31 +02:00
else :
# no match; create new attribute
attrib_obj = attrclass ( db_key = attribute_name , db_obj = self )
2012-03-30 23:47:22 +02:00
# re-set an old attribute value
2012-10-14 19:29:56 +02:00
try :
attrib_obj . value = new_value
except IntegrityError :
# this can happen if the cache was stale and the databse object is
# missing. If so we need to clean self.hashid from the cache
if _GA ( self , " hashid " ) in _ATTRIBUTE_CACHE :
del _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ]
self . delete ( )
raise IntegrityError ( " Attribute could not be saved - object %s was deleted from database. " % self . key )
2012-04-28 14:47:11 +02:00
_ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] [ attribute_name ] = attrib_obj
2012-03-30 23:47:22 +02:00
2012-06-10 21:46:00 +02:00
def get_attribute_obj ( self , attribute_name , default = None ) :
"""
Get the actual attribute object named attribute_name
"""
attrib_obj = _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] . get ( attribute_name )
if not attrib_obj :
attrib_obj = _GA ( self , " _attribute_class " ) . objects . filter (
db_obj = self ) . filter ( db_key__iexact = attribute_name )
if not attrib_obj :
return default
_ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] [ attribute_name ] = attrib_obj [ 0 ] #query is first evaluated here
return _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] [ attribute_name ]
return attrib_obj
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-03-30 23:47:22 +02: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-04-28 14:47:11 +02:00
attrib_obj = _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] . get ( attribute_name )
2012-04-26 21:39:16 +02:00
if not attrib_obj :
2012-03-31 13:06:29 +02:00
attrib_obj = _GA ( self , " _attribute_class " ) . objects . filter (
2012-04-26 21:39:16 +02:00
db_obj = self ) . filter ( db_key__iexact = attribute_name )
if not attrib_obj :
return default
2012-04-28 14:47:11 +02:00
_ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] [ attribute_name ] = attrib_obj [ 0 ] #query is first evaluated here
return _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] [ attribute_name ] . value
2012-02-05 21:04:10 +01:00
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.
"""
2012-04-28 14:47:11 +02:00
attrib_obj = _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] . get ( attribute_name )
2012-04-26 21:39:16 +02:00
if not attrib_obj :
attrib_obj = _GA ( self , " _attribute_class " ) . objects . filter (
db_obj = self ) . filter ( db_key__iexact = attribute_name )
if not attrib_obj :
raise AttributeError
2012-04-28 14:47:11 +02:00
_ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] [ attribute_name ] = attrib_obj [ 0 ] #query is first evaluated here
return _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] [ attribute_name ] . value
2012-04-26 21:39:16 +02:00
return attrib_obj . value
2012-03-30 23:47:22 +02:00
2010-08-29 18:46:58 +00:00
def del_attribute ( self , attribute_name ) :
"""
Removes an attribute entirely .
2012-03-30 23:47:22 +02:00
2010-08-29 18:46:58 +00:00
attribute_name : ( str ) The attribute ' s name.
"""
2012-04-28 14:47:11 +02:00
attr_obj = _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] . get ( attribute_name )
2012-04-26 21:39:16 +02:00
if attr_obj :
2012-04-28 14:47:11 +02:00
del _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] [ attribute_name ]
2012-04-26 21:39:16 +02:00
attr_obj . delete ( )
else :
try :
_GA ( self , " _attribute_class " ) . objects . filter (
2012-02-05 21:04:10 +01:00
db_obj = self ) . filter ( db_key__iexact = attribute_name ) [ 0 ] . delete ( )
2012-04-26 21:39:16 +02:00
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 ) :
"""
2012-03-30 23:47:22 +02:00
Removes and attribute . Raises AttributeError if
attribute is not found .
2012-02-05 21:04:10 +01:00
attribute_name : ( str ) The attribute ' s name.
"""
2012-04-28 14:47:11 +02:00
attr_obj = _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] . get ( attribute_name )
2012-04-26 21:39:16 +02:00
if attr_obj :
2012-04-28 14:47:11 +02:00
del _ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] [ attribute_name ]
2012-04-26 21:39:16 +02:00
attr_obj . delete ( )
else :
try :
_GA ( self , " _attribute_class " ) . objects . filter (
2012-02-05 21:04:10 +01:00
db_obj = self ) . filter ( db_key__iexact = attribute_name ) [ 0 ] . delete ( )
2012-04-26 21:39:16 +02:00
except IndexError :
pass
raise AttributeError
2010-08-29 18:46:58 +00:00
def get_all_attributes ( self ) :
"""
Returns all attributes defined on the object .
2012-03-30 23:47:22 +02:00
"""
2012-03-31 13:06:29 +02:00
return list ( _GA ( 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 .
2012-03-30 23:47:22 +02:00
set delete = True to delete the named attribute .
2010-08-29 18:46:58 +00:00
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
"""
2012-03-30 23:47:22 +02:00
if attribute_name == None :
2010-08-29 18:46:58 +00:00
# act as a list method
2012-03-30 23:47:22 +02:00
return self . get_all_attributes ( )
2010-08-29 18:46:58 +00:00
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 )
2012-06-10 21:46:00 +02:00
def secure_attr ( self , accessing_object , attribute_name = None , value = None , delete = False ,
default_access_read = True , default_access_edit = True , default_access_create = True ) :
"""
This is a version of attr that requires the accessing object
as input and will use that to check eventual access locks on
the Attribute before allowing any changes or reads .
In the cases when this method wouldn ' t return, it will return
True for a successful operation , None otherwise .
locktypes checked on the Attribute itself :
attrread - control access to reading the attribute value
attredit - control edit / delete access
locktype checked on the object on which the Attribute is / will be stored :
attrcreate - control attribute create access ( this is checked * on the object * not on the Attribute ! )
default_access_ * defines which access is assumed if no
suitable lock is defined on the Atttribute .
"""
if attribute_name == None :
# act as list method, but check access
return [ attr for attr in self . get_all_attributes ( )
if attr . access ( accessing_object , " attread " , default = default_access_read ) ]
elif delete == True :
# act as deleter
attr = self . get_attribute_obj ( attribute_name )
if attr and attr . access ( accessing_object , " attredit " , default = default_access_edit ) :
self . del_attribute ( attribute_name )
return True
elif value == None :
# act as getter
attr = self . get_attribute_obj ( attribute_name )
if attr and attr . access ( accessing_object , " attrread " , default = default_access_read ) :
return attr . value
else :
# act as setter
attr = self . get_attribute_obj ( attribute_name )
if attr :
# attribute already exists
if attr . access ( accessing_object , " attredit " , default = default_access_edit ) :
self . set_attribute ( attribute_name , value )
return True
else :
# creating a new attribute - check access on storing object!
if self . access ( accessing_object , " attrcreate " , default = default_access_create ) :
self . set_attribute ( attribute_name , value )
return True
2010-08-29 18:46:58 +00:00
#@property
2012-03-31 13:06:29 +02:00
def __db_get ( self ) :
2010-08-29 18:46:58 +00:00
"""
A second convenience wrapper for the the attribute methods . It
allows for the syntax
obj . db . attrname = value
and
2012-03-30 23:47:22 +02:00
value = obj . db . attrname
and
del obj . db . attrname
2010-08-29 18:46:58 +00:00
and
2012-06-20 23:22:26 +02:00
all_attr = obj . db . all ( unless there is no attribute named ' all ' , in which
2012-03-30 23:47:22 +02:00
case that will be returned instead ) .
2010-08-29 18:46:58 +00:00
"""
try :
return self . _db_holder
except AttributeError :
class DbHolder ( object ) :
" Holder for allowing property access of attributes "
def __init__ ( self , obj ) :
2012-03-31 13:06:29 +02:00
_SA ( self , ' obj ' , obj )
2012-03-30 23:47:22 +02:00
def __getattribute__ ( self , attrname ) :
2010-08-29 18:46:58 +00:00
if attrname == ' all ' :
2012-06-20 23:22:26 +02:00
# we allow to overload our default .all
2012-03-31 13:06:29 +02:00
attr = _GA ( self , ' obj ' ) . get_attribute ( " all " )
2010-08-29 18:46:58 +00:00
if attr :
return attr
2012-03-31 13:06:29 +02:00
return _GA ( self , ' all ' )
return _GA ( self , ' obj ' ) . get_attribute ( attrname )
2012-03-30 23:47:22 +02:00
def __setattr__ ( self , attrname , value ) :
2012-03-31 13:06:29 +02:00
_GA ( self , ' obj ' ) . set_attribute ( attrname , value )
2012-03-30 23:47:22 +02:00
def __delattr__ ( self , attrname ) :
2012-03-31 13:06:29 +02:00
_GA ( self , ' obj ' ) . del_attribute ( attrname )
2012-04-21 16:15:37 +02:00
def get_all ( self ) :
2012-03-31 13:06:29 +02:00
return _GA ( self , ' obj ' ) . get_all_attributes ( )
2012-04-21 16:15:37 +02:00
all = property ( get_all )
2010-08-29 18:46:58 +00:00
self . _db_holder = DbHolder ( self )
return self . _db_holder
#@db.setter
2012-03-31 13:06:29 +02:00
def __db_set ( self , value ) :
2010-08-29 18:46:58 +00:00
" 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
2012-03-31 13:06:29 +02:00
def __db_del ( self ) :
2010-08-29 18:46:58 +00:00
" Stop accidental deletion. "
raise Exception ( " Cannot delete the db object! " )
2012-03-31 13:06:29 +02:00
db = property ( __db_get , __db_set , __db_del )
2010-08-29 18:46:58 +00:00
#
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
2012-09-18 01:03:35 +02:00
stores . Will not raise error but return None .
2010-08-29 18:46:58 +00:00
"""
2012-03-30 23:47:22 +02:00
if attribute_name == None :
2010-08-29 18:46:58 +00:00
# act as a list method
if callable ( self . ndb . all ) :
return self . ndb . all ( )
else :
2012-03-30 23:47:22 +02:00
return [ val for val in self . ndb . __dict__ . keys ( )
if not val . startswith [ ' _ ' ] ]
2010-08-29 18:46:58 +00:00
elif delete == True :
if hasattr ( self . ndb , attribute_name ) :
2012-09-18 01:03:35 +02:00
_DA ( _GA ( 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-09-18 01:03:35 +02:00
_GA ( _GA ( self , " ndb " ) , attribute_name )
2010-08-29 18:46:58 +00:00
else :
2012-03-30 23:47:22 +02:00
return None
2010-08-29 18:46:58 +00:00
else :
# act as a setter
2012-03-31 13:06:29 +02:00
_SA ( self . db , attribute_name , value )
2012-03-30 23:47:22 +02:00
2010-08-29 18:46:58 +00:00
#@property
2012-03-31 13:06:29 +02:00
def __ndb_get ( self ) :
2010-08-29 18:46:58 +00:00
"""
2012-03-30 23:47:22 +02:00
A non - persistent store ( ndb : NonDataBase ) . Everything stored
2010-08-29 18:46:58 +00:00
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. "
2012-04-21 16:15:37 +02:00
def get_all ( self ) :
2012-03-30 23:47:22 +02:00
return [ val for val in self . __dict__ . keys ( )
2012-04-21 16:15:37 +02:00
if not val . startswith ( ' _ ' ) ]
all = property ( get_all )
2011-08-11 21:16:35 +00:00
def __getattribute__ ( self , key ) :
2012-03-30 23:47:22 +02:00
# return None if no matching attribute was found.
2011-08-11 21:16:35 +00:00
try :
2012-03-31 13:06:29 +02:00
return _GA ( self , key )
2011-08-11 21:16:35 +00:00
except AttributeError :
2012-03-30 23:47:22 +02:00
return None
2010-08-29 18:46:58 +00:00
self . _ndb_holder = NdbHolder ( )
return self . _ndb_holder
#@ndb.setter
2012-03-31 13:06:29 +02:00
def __ndb_set ( self , value ) :
2010-08-29 18:46:58 +00:00
" Stop accidentally replacing the db object "
string = " Cannot assign directly to ndb object! "
string = " Use ndb.attr=value instead. "
raise Exception ( string )
#@ndb.deleter
2012-03-31 13:06:29 +02:00
def __ndb_del ( self ) :
2010-08-29 18:46:58 +00:00
" Stop accidental deletion. "
raise Exception ( " Cannot delete the ndb object! " )
2012-03-31 13:06:29 +02:00
ndb = property ( __ndb_get , __ndb_set , __ndb_del )
2012-03-30 23:47:22 +02: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
2012-03-30 23:47:22 +02:00
"""
2011-03-15 16:08:32 +00:00
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 :
2012-03-30 23:47:22 +02:00
return True
2011-04-23 11:54:08 +00:00
2011-03-15 16:08:32 +00:00
if not permstring :
2012-03-30 23:47:22 +02:00
return False
2011-03-15 16:08:32 +00:00
perm = permstring . lower ( )
if perm in [ p . lower ( ) for p in self . permissions ] :
# simplest case - we have a direct match
2012-03-30 23:47:22 +02:00
return True
2012-03-31 13:06:29 +02:00
if perm in _PERMISSION_HIERARCHY :
2011-03-15 16:08:32 +00:00
# check if we have a higher hierarchy position
2012-03-31 13:06:29 +02:00
ppos = _PERMISSION_HIERARCHY . index ( perm )
return any ( True for hpos , hperm in enumerate ( _PERMISSION_HIERARCHY )
2011-03-15 16:08:32 +00:00
if hperm in [ p . lower ( ) for p in self . permissions ] and hpos > ppos )
2012-03-30 23:47:22 +02:00
return False
2012-04-15 21:46:43 +02:00
2012-09-29 16:02:43 +02:00
def flush_attr_cache ( self ) :
"""
Flush only the attribute cache for this object .
"""
_ATTRIBUTE_CACHE [ _GA ( self , " hashid " ) ] = { }
2012-04-15 21:46:43 +02:00
def flush_from_cache ( self ) :
"""
Flush this object instance from cache , forcing an object reload . Note that this
will kill all temporary attributes on this object since it will be recreated
as a new Typeclass instance .
"""
self . __class__ . flush_cached_instance ( self )