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
2014-02-16 23:29:19 +01:00
import re
2010-08-29 18:46:58 +00:00
import traceback
2014-05-15 10:05:41 +02:00
import weakref
2014-12-19 16:29:41 +01:00
from importlib import import_module
2012-04-28 00:37:36 +02:00
2013-07-08 18:13:21 +02:00
from django . db import models
2014-06-15 12:27:48 +02:00
from django . core . exceptions import ObjectDoesNotExist
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
2013-08-24 21:23:43 +02:00
2014-07-05 20:32:08 +02:00
from src . utils . idmapper . models import SharedMemoryModel
2013-10-21 21:17:32 +02:00
from src . server . caches import get_prop_cache , set_prop_cache
2014-02-16 21:27:42 +01:00
#from src.server.caches import set_attr_cache
2013-07-08 18:13:21 +02:00
2013-05-29 16:16:28 +02:00
#from src.server.caches import call_ndb_hooks
2014-12-20 19:19:48 +01: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
2014-12-20 19:19:48 +01:00
#from src.utils import logger
2014-12-20 19:04:49 +01:00
from django . db . models . base import ModelBase
2014-06-28 18:01:00 -05:00
from src . utils . utils import (
2014-07-06 13:10:03 +02:00
make_iter , is_iter , to_str , inherits_from , lazy_property )
2013-04-13 15:15:02 +02:00
from src . utils . dbserialize import to_pickle , from_pickle
from src . utils . picklefield import PickledObjectField
2010-08-29 18:46:58 +00:00
2012-03-31 15:09:22 +02:00
__all__ = ( " Attribute " , " TypeNick " , " TypedObject " )
2014-06-14 19:31:19 +02:00
TICKER_HANDLER = None
2012-03-31 13:06:29 +02:00
_PERMISSION_HIERARCHY = [ p . lower ( ) for p in settings . PERMISSION_HIERARCHY ]
2013-10-20 21:02:37 +02:00
_TYPECLASS_AGGRESSIVE_CACHE = settings . TYPECLASS_AGGRESSIVE_CACHE
2011-03-15 16:08:32 +00:00
2012-03-31 13:06:29 +02:00
_GA = object . __getattribute__
_SA = object . __setattr__
_DA = object . __delattr__
2012-02-05 21:04:10 +01:00
2013-07-08 18:13:21 +02:00
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
#
#------------------------------------------------------------
2014-06-28 17:38:21 +02:00
class Attribute ( SharedMemoryModel ) :
2010-08-29 18:46:58 +00:00
"""
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
2013-04-10 21:45:56 +02:00
lock_storage - perm strings
2010-08-29 18:46:58 +00:00
obj - which object the attribute is defined on
2013-07-09 00:09:19 +02:00
date_created - when the attribute was created .
value - the data stored in the attribute , in pickled form
using wrappers to be able to store / retrieve models .
2013-08-24 21:23:43 +02:00
strvalue - string - only data . This data is not pickled and is
thus faster to search for in the database .
category - optional character string for grouping the Attribute
2012-02-14 23:40:16 +01:00
2010-08-29 18:46:58 +00:00
"""
#
# Attribute Database Model setup
#
2013-05-29 18:47:51 +02:00
# These database fields are all set using their corresponding properties,
2013-08-04 12:47:00 -05:00
# named same as the field, but withtout the db_* prefix.
2013-08-24 23:57:44 +02:00
db_key = models . CharField ( ' key ' , max_length = 255 , db_index = True )
2014-06-28 18:01:00 -05:00
db_value = PickledObjectField (
' value ' , null = True ,
help_text = " The data returned when the attribute is accessed. Must be "
" written as a Python literal if editing through the admin "
2014-06-29 09:14:01 -05:00
" interface. Attribute values which are not Python literals "
" cannot be edited through the admin interface. " )
2014-06-28 18:01:00 -05:00
db_strvalue = models . TextField (
' strvalue ' , null = True , blank = True ,
help_text = " String-specific storage for quick look-up " )
db_category = models . CharField (
' category ' , max_length = 128 , db_index = True , blank = True , null = True ,
help_text = " Optional categorization of attribute. " )
2012-03-30 23:47:22 +02:00
# Lock storage
2014-06-28 18:01:00 -05:00
db_lock_storage = models . TextField (
' locks ' , blank = True ,
help_text = " Lockstrings for this object are stored here. " )
db_model = models . CharField (
' model ' , max_length = 32 , db_index = True , blank = True , null = True ,
help_text = " Which model of object this attribute is attached to (A "
" natural key like objects.dbobject). You should not change "
" this value unless you know what you are doing. " )
2014-02-16 15:10:47 +01:00
# subclass of Attribute (None or nick)
2014-06-28 18:01:00 -05:00
db_attrtype = models . CharField (
' attrtype ' , max_length = 16 , db_index = True , blank = True , null = True ,
help_text = " Subclass of Attribute (None or nick) " )
2010-08-29 18:46:58 +00:00
# time stamp
2014-06-28 18:01:00 -05:00
db_date_created = models . DateTimeField (
' date_created ' , editable = False , auto_now_add = True )
2012-03-30 23:47:22 +02:00
# Database manager
2014-10-25 22:40:38 +02:00
#objects = managers.AttributeManager()
2012-03-30 23:47:22 +02:00
2014-07-06 13:10:03 +02:00
@lazy_property
def locks ( self ) :
return LockHandler ( self )
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
verbose_name = " Evennia Attribute "
2012-02-14 23:40:16 +01:00
2014-05-11 08:57:36 +02:00
# read-only wrappers
key = property ( lambda self : self . db_key )
strvalue = property ( lambda self : self . db_strvalue )
category = property ( lambda self : self . db_category )
model = property ( lambda self : self . db_model )
attrtype = property ( lambda self : self . db_attrtype )
date_created = property ( lambda self : self . db_date_created )
2014-05-11 19:41:37 +02:00
def __lock_storage_get ( self ) :
return self . db_lock_storage
def __lock_storage_set ( self , value ) :
self . db_lock_storage = value
self . save ( update_fields = [ " db_lock_storage " ] )
def __lock_storage_del ( self ) :
self . db_lock_storage = " "
self . save ( update_fields = [ " db_lock_storage " ] )
lock_storage = property ( __lock_storage_get , __lock_storage_set , __lock_storage_del )
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).
# 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
"""
2013-11-12 19:32:24 +01:00
Getter . Allows for value = self . value .
We cannot cache here since it makes certain cases ( such
2013-11-27 16:57:41 +01:00
as storing a dbobj which is then deleted elsewhere ) out - of - sync .
2013-11-12 19:32:24 +01:00
The overhead of unpickling seems hard to avoid .
2012-03-30 23:47:22 +02:00
"""
2013-11-12 19:32:24 +01:00
return from_pickle ( self . db_value , db_obj = self )
2013-11-14 19:31:17 +01:00
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
"""
2013-11-27 16:57:41 +01:00
Setter . Allows for self . value = value . We cannot cache here ,
see self . __value_get .
2012-03-30 23:47:22 +02:00
"""
2013-11-12 19:32:24 +01:00
self . db_value = to_pickle ( new_value )
2014-05-11 08:57:36 +02:00
self . save ( update_fields = [ " db_value " ] )
2013-11-14 19:31:17 +01: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
#
#
# Attribute methods
#
#
def __str__ ( self ) :
2013-07-13 15:39:16 +02:00
return smart_str ( " %s ( %s ) " % ( _GA ( self , " db_key " ) , _GA ( self , " id " ) ) )
2012-03-30 23:47:22 +02:00
2010-08-29 18:46:58 +00:00
def __unicode__ ( self ) :
2013-08-24 21:23:43 +02:00
return u " %s ( %s ) " % ( _GA ( self , " db_key " ) , _GA ( self , " id " ) )
2010-08-29 18:46:58 +00:00
2014-01-18 23:56:07 +01:00
def access ( self , accessing_obj , access_type = ' read ' , default = False , * * kwargs ) :
2011-03-15 16:08:32 +00:00
"""
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
2014-01-18 23:56:07 +01:00
* * kwargs - passed to at_access hook along with result .
2012-03-30 23:47:22 +02:00
"""
2014-01-18 23:56:07 +01:00
result = self . locks . check ( accessing_obj , access_type = access_type , default = default )
2014-05-11 08:57:36 +02:00
#self.at_access(result, **kwargs)
2014-01-18 23:56:07 +01:00
return result
2011-03-15 16:08:32 +00:00
2013-11-14 19:31:17 +01:00
2013-07-12 11:12:21 +02:00
#
2013-08-24 21:23:43 +02:00
# Handlers making use of the Attribute model
2013-07-12 11:12:21 +02:00
#
2013-08-24 21:23:43 +02:00
class AttributeHandler ( object ) :
2013-07-12 11:12:21 +02:00
"""
2013-08-24 21:23:43 +02:00
Handler for adding Attributes to the object .
"""
_m2m_fieldname = " db_attributes "
_attrcreate = " attrcreate "
_attredit = " attredit "
_attrread = " attrread "
2014-02-16 15:10:47 +01:00
_attrtype = None
2013-07-12 11:12:21 +02:00
2013-08-24 21:23:43 +02:00
def __init__ ( self , obj ) :
" Initialize handler "
self . obj = obj
2014-07-05 20:32:08 +02:00
self . _objid = obj . id
2014-07-06 16:03:27 +02:00
self . _model = to_str ( obj . __class__ . __name__ . lower ( ) )
2013-10-20 21:02:37 +02:00
self . _cache = None
def _recache ( self ) :
2014-07-05 20:32:08 +02:00
" Cache all attributes of this object "
query = { " %s __id " % self . _model : self . _objid ,
" attribute__db_attrtype " : self . _attrtype }
attrs = [ conn . attribute for conn in getattr ( self . obj , self . _m2m_fieldname ) . through . objects . filter ( * * query ) ]
2014-02-16 15:10:47 +01:00
self . _cache = dict ( ( " %s - %s " % ( to_str ( attr . db_key ) . lower ( ) ,
2014-07-05 20:32:08 +02:00
attr . db_category . lower ( ) if conn . attribute . db_category else None ) ,
attr ) for attr in attrs )
2013-07-12 11:12:21 +02:00
2013-08-24 21:23:43 +02:00
def has ( self , key , category = None ) :
"""
2013-11-14 19:31:17 +01:00
Checks if the given Attribute ( or list of Attributes ) exists on
the object .
2013-07-12 11:12:21 +02:00
2013-08-24 21:23:43 +02:00
If an iterable is given , returns list of booleans .
"""
2013-11-14 19:31:17 +01:00
if self . _cache is None or not _TYPECLASS_AGGRESSIVE_CACHE :
2013-10-20 21:02:37 +02:00
self . _recache ( )
2014-02-16 21:27:42 +01:00
key = [ k . strip ( ) . lower ( ) for k in make_iter ( key ) if k ]
2014-02-16 15:10:47 +01:00
category = category . strip ( ) . lower ( ) if category is not None else None
searchkeys = [ " %s - %s " % ( k , category ) for k in make_iter ( key ) ]
2014-02-16 21:27:42 +01:00
ret = [ self . _cache . get ( skey ) for skey in searchkeys if skey in self . _cache ]
2013-10-20 21:02:37 +02:00
return ret [ 0 ] if len ( ret ) == 1 else ret
2013-08-24 21:23:43 +02:00
2013-11-14 19:31:17 +01:00
def get ( self , key = None , category = None , default = None , return_obj = False ,
strattr = False , raise_exception = False , accessing_obj = None ,
2014-02-27 16:07:39 +01:00
default_access = True , not_found_none = False ) :
2013-08-24 21:23:43 +02:00
"""
Returns the value of the given Attribute or list of Attributes .
strattr will cause the string - only value field instead of the normal
pickled field data . Use to get back values from Attributes added with
the strattr keyword .
If return_obj = True , return the matching Attribute object
2014-02-27 16:07:39 +01:00
instead . Returns default if no matches ( or [ ] if key was a list
2013-08-24 21:23:43 +02:00
with no matches ) . If raise_exception = True , failure to find a
match will raise AttributeError instead .
If accessing_obj is given , its " attrread " permission lock will be
checked before displaying each looked - after Attribute . If no
accessing_obj is given , no check will be done .
"""
2014-04-25 09:01:49 +02:00
class RetDefault ( object ) :
" Holds default values "
def __init__ ( self ) :
self . value = default
2014-05-03 23:51:08 +02:00
self . strvalue = str ( default ) if default is not None else None
2014-04-25 09:01:49 +02:00
2013-11-14 19:31:17 +01:00
if self . _cache is None or not _TYPECLASS_AGGRESSIVE_CACHE :
2013-10-20 21:02:37 +02:00
self . _recache ( )
2013-08-24 21:23:43 +02:00
ret = [ ]
2014-02-16 21:27:42 +01:00
key = [ k . strip ( ) . lower ( ) for k in make_iter ( key ) if k ]
2014-02-16 15:10:47 +01:00
category = category . strip ( ) . lower ( ) if category is not None else None
2014-02-16 21:27:42 +01:00
#print "cache:", self._cache.keys(), key
2013-10-21 22:51:16 +02:00
if not key :
# return all with matching category (or no category)
2014-02-16 21:27:42 +01:00
catkey = " - %s " % category if category is not None else None
ret = [ attr for key , attr in self . _cache . items ( ) if key and key . endswith ( catkey ) ]
2013-10-21 22:51:16 +02:00
else :
2014-02-16 15:10:47 +01:00
for searchkey in ( " %s - %s " % ( k , category ) for k in key ) :
attr_obj = self . _cache . get ( searchkey )
2013-10-21 22:51:16 +02:00
if attr_obj :
ret . append ( attr_obj )
2013-10-20 21:02:37 +02:00
else :
2013-10-21 22:51:16 +02:00
if raise_exception :
raise AttributeError
else :
2014-04-25 09:01:49 +02:00
ret . append ( RetDefault ( ) )
2013-08-24 21:23:43 +02:00
if accessing_obj :
# check 'attrread' locks
ret = [ attr for attr in ret if attr . access ( accessing_obj , self . _attrread , default = default_access ) ]
if strattr :
2014-02-16 21:27:42 +01:00
ret = ret if return_obj else [ attr . strvalue for attr in ret if attr ]
2013-08-24 21:23:43 +02:00
else :
2014-02-16 21:27:42 +01:00
ret = ret if return_obj else [ attr . value for attr in ret if attr ]
2014-02-27 16:07:39 +01:00
if not ret :
return ret if len ( key ) > 1 else default
2013-08-24 21:23:43 +02:00
return ret [ 0 ] if len ( ret ) == 1 else ret
2013-07-12 11:12:21 +02:00
2014-07-01 00:46:20 +02:00
2013-11-14 19:31:17 +01:00
def add ( self , key , value , category = None , lockstring = " " ,
strattr = False , accessing_obj = None , default_access = True ) :
2013-08-24 21:23:43 +02:00
"""
Add attribute to object , with optional lockstring .
2013-07-12 11:12:21 +02:00
2013-11-14 19:31:17 +01:00
If strattr is set , the db_strvalue field will be used ( no pickling ) .
Use the get ( ) method with the strattr keyword to get it back .
2013-07-12 11:12:21 +02:00
2013-08-24 21:23:43 +02:00
If accessing_obj is given , self . obj ' s ' attrcreate ' lock access
2013-11-14 19:31:17 +01:00
will be checked against it . If no accessing_obj is given , no check
will be done .
2014-07-01 01:10:44 +02:00
"""
if accessing_obj and not self . obj . access ( accessing_obj ,
self . _attrcreate , default = default_access ) :
# check create access
return
if self . _cache is None :
self . _recache ( )
if not key :
return
2014-07-01 00:46:20 +02:00
2014-07-01 01:10:44 +02:00
category = category . strip ( ) . lower ( ) if category is not None else None
keystr = key . strip ( ) . lower ( )
cachekey = " %s - %s " % ( keystr , category )
attr_obj = self . _cache . get ( cachekey )
if attr_obj :
# update an existing attribute object
if strattr :
# store as a simple string (will not notify OOB handlers)
attr_obj . db_strvalue = value
attr_obj . save ( update_fields = [ " db_strvalue " ] )
else :
# store normally (this will also notify OOB handlers)
attr_obj . value = value
else :
# create a new Attribute (no OOB handlers can be notified)
kwargs = { " db_key " : keystr , " db_category " : category ,
" db_model " : self . _model , " db_attrtype " : self . _attrtype ,
" db_value " : None if strattr else to_pickle ( value ) ,
" db_strvalue " : value if strattr else None }
new_attr = Attribute ( * * kwargs )
new_attr . save ( )
getattr ( self . obj , self . _m2m_fieldname ) . add ( new_attr )
self . _cache [ cachekey ] = new_attr
def batch_add ( self , key , value , category = None , lockstring = " " ,
strattr = False , accessing_obj = None , default_access = True ) :
"""
Batch - version of add ( ) . This is more efficient than
repeat - calling add .
key and value must be sequences of the same length , each
representing a key - value pair .
2014-07-01 00:46:20 +02:00
2013-08-24 21:23:43 +02:00
"""
2013-11-14 19:31:17 +01:00
if accessing_obj and not self . obj . access ( accessing_obj ,
self . _attrcreate , default = default_access ) :
2013-08-24 21:23:43 +02:00
# check create access
return
2013-11-14 19:31:17 +01:00
if self . _cache is None :
2013-10-20 21:02:37 +02:00
self . _recache ( )
2014-02-16 21:27:42 +01:00
if not key :
return
2014-07-01 00:46:20 +02:00
keys , values = make_iter ( key ) , make_iter ( value )
if len ( keys ) != len ( values ) :
raise RuntimeError ( " AttributeHandler.add(): key and value of different length: %s vs %s " % key , value )
2014-02-16 15:10:47 +01:00
category = category . strip ( ) . lower ( ) if category is not None else None
2014-07-01 00:46:20 +02:00
new_attrobjs = [ ]
for ikey , keystr in enumerate ( keys ) :
keystr = keystr . strip ( ) . lower ( )
new_value = values [ ikey ]
cachekey = " %s - %s " % ( keystr , category )
attr_obj = self . _cache . get ( cachekey )
if attr_obj :
# update an existing attribute object
if strattr :
# store as a simple string (will not notify OOB handlers)
attr_obj . db_strvalue = new_value
attr_obj . save ( update_fields = [ " db_strvalue " ] )
else :
# store normally (this will also notify OOB handlers)
attr_obj . value = new_value
else :
# create a new Attribute (no OOB handlers can be notified)
kwargs = { " db_key " : keystr , " db_category " : category ,
2014-07-05 20:32:08 +02:00
" db_attrtype " : self . _attrtype ,
2014-07-01 00:46:20 +02:00
" db_value " : None if strattr else to_pickle ( new_value ) ,
" db_strvalue " : value if strattr else None }
new_attr = Attribute ( * * kwargs )
new_attr . save ( )
new_attrobjs . append ( new_attr )
if new_attrobjs :
# Add new objects to m2m field all at once
getattr ( self . obj , self . _m2m_fieldname ) . add ( * new_attrobjs )
self . _recache ( )
2013-08-24 21:23:43 +02:00
2014-07-01 01:10:44 +02:00
2013-11-14 19:31:17 +01:00
def remove ( self , key , raise_exception = False , category = None ,
accessing_obj = None , default_access = True ) :
2013-08-24 21:23:43 +02:00
""" Remove attribute or a list of attributes from object.
2013-11-14 19:31:17 +01:00
If accessing_obj is given , will check against the ' attredit ' lock .
If not given , this check is skipped .
2013-08-24 21:23:43 +02:00
"""
2013-11-14 19:31:17 +01:00
if self . _cache is None or not _TYPECLASS_AGGRESSIVE_CACHE :
2013-10-20 21:02:37 +02:00
self . _recache ( )
2014-02-16 21:27:42 +01:00
key = [ k . strip ( ) . lower ( ) for k in make_iter ( key ) if k ]
2014-02-16 15:10:47 +01:00
category = category . strip ( ) . lower ( ) if category is not None else None
for searchstr in ( " %s - %s " % ( k , category ) for k in key ) :
attr_obj = self . _cache . get ( searchstr )
2013-10-20 21:02:37 +02:00
if attr_obj :
2014-02-16 15:10:47 +01:00
if not ( accessing_obj and not attr_obj . access ( accessing_obj ,
self . _attredit , default = default_access ) ) :
attr_obj . delete ( )
2013-10-20 21:02:37 +02:00
elif not attr_obj and raise_exception :
raise AttributeError
self . _recache ( )
2013-08-24 21:23:43 +02:00
2013-08-25 19:15:27 +02:00
def clear ( self , category = None , accessing_obj = None , default_access = True ) :
2013-08-24 21:23:43 +02:00
"""
Remove all Attributes on this object . If accessing_obj is
given , check the ' attredit ' lock on each Attribute before
continuing . If not given , skip check .
"""
2014-04-20 16:47:03 +02:00
if self . _cache is None or not _TYPECLASS_AGGRESSIVE_CACHE :
self . _recache ( )
if accessing_obj :
[ attr . delete ( ) for attr in self . _cache . values ( )
if attr . access ( accessing_obj , self . _attredit , default = default_access ) ]
else :
[ attr . delete ( ) for attr in self . _cache . values ( ) ]
2013-10-20 21:02:37 +02:00
self . _recache ( )
2013-08-24 21:23:43 +02:00
2014-02-16 21:27:42 +01:00
def all ( self , accessing_obj = None , default_access = True ) :
2013-08-24 21:23:43 +02:00
"""
Return all Attribute objects on this object .
If accessing_obj is given , check the " attrread " lock on
each attribute before returning them . If not given , this
check is skipped .
"""
2013-11-14 19:31:17 +01:00
if self . _cache is None or not _TYPECLASS_AGGRESSIVE_CACHE :
2013-10-20 21:02:37 +02:00
self . _recache ( )
2014-07-07 22:47:26 +02:00
attrs = sorted ( self . _cache . values ( ) , key = lambda o : o . id )
2014-04-20 16:47:03 +02:00
if accessing_obj :
2014-07-07 22:47:26 +02:00
return [ attr for attr in attrs
2014-04-20 16:47:03 +02:00
if attr . access ( accessing_obj , self . _attredit , default = default_access ) ]
else :
2014-07-07 22:47:26 +02:00
return attrs
2013-10-20 21:02:37 +02:00
2014-07-01 01:10:44 +02:00
2013-08-24 21:23:43 +02:00
class NickHandler ( AttributeHandler ) :
"""
Handles the addition and removal of Nicks
( uses Attributes ' strvalue and category fields)
2013-09-21 17:33:48 +02:00
Nicks are stored as Attributes
with categories nick_ < nicktype >
2013-08-24 21:23:43 +02:00
"""
2014-02-16 15:10:47 +01:00
_attrtype = " nick "
2013-08-24 21:23:43 +02:00
def has ( self , key , category = " inputline " ) :
return super ( NickHandler , self ) . has ( key , category = category )
2013-09-22 22:34:22 +02:00
2013-09-21 22:00:46 +02:00
def get ( self , key = None , category = " inputline " , * * kwargs ) :
2013-08-24 21:23:43 +02:00
" Get the replacement value matching the given key and category "
2013-09-21 22:00:46 +02:00
return super ( NickHandler , self ) . get ( key = key , category = category , strattr = True , * * kwargs )
2013-09-22 22:34:22 +02:00
def add ( self , key , replacement , category = " inputline " , * * kwargs ) :
" Add a new nick "
super ( NickHandler , self ) . add ( key , replacement , category = category , strattr = True , * * kwargs )
2013-08-24 21:23:43 +02:00
def remove ( self , key , category = " inputline " , * * kwargs ) :
" Remove Nick with matching category "
super ( NickHandler , self ) . remove ( key , category = category , * * kwargs )
2013-07-12 11:12:21 +02:00
2014-02-16 23:29:19 +01:00
def nickreplace ( self , raw_string , categories = ( " inputline " , " channel " ) , include_player = True ) :
2014-02-16 21:27:42 +01:00
" Replace entries in raw_string with nick replacement "
2014-02-16 23:29:19 +01:00
raw_string
2014-02-16 22:31:59 +01:00
obj_nicks , player_nicks = [ ] , [ ]
2014-02-16 21:27:42 +01:00
for category in make_iter ( categories ) :
2014-02-27 16:07:39 +01:00
obj_nicks . extend ( [ n for n in make_iter ( self . get ( category = category , return_obj = True ) ) if n ] )
2014-02-16 21:27:42 +01:00
if include_player and self . obj . has_player :
for category in make_iter ( categories ) :
2014-02-27 16:07:39 +01:00
player_nicks . extend ( [ n for n in make_iter ( self . obj . player . nicks . get ( category = category , return_obj = True ) ) if n ] )
2014-02-16 21:27:42 +01:00
for nick in obj_nicks + player_nicks :
2014-02-16 23:29:19 +01:00
# make a case-insensitive match here
match = re . match ( re . escape ( nick . db_key ) , raw_string , re . IGNORECASE )
if match :
raw_string = raw_string . replace ( match . group ( ) , nick . db_strvalue , 1 )
2014-02-16 21:27:42 +01:00
break
return raw_string
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
class NAttributeHandler ( object ) :
"""
2014-05-11 01:05:59 +02:00
This stand - alone handler manages non - database saving .
2014-08-05 09:05:44 +02:00
It is similar to AttributeHandler and is used
2014-05-11 01:05:59 +02:00
by the . ndb handler in the same way as . db does
for the AttributeHandler .
2013-10-15 20:00:18 +02:00
"""
def __init__ ( self , obj ) :
" initialized on the object "
2014-05-11 01:05:59 +02:00
self . _store = { }
2014-05-15 10:05:41 +02:00
self . obj = weakref . proxy ( obj )
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
def has ( self , key ) :
" Check if object has this attribute or not "
2014-05-11 01:05:59 +02:00
return key in self . _store
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
def get ( self , key ) :
" Returns named key value "
2014-05-11 01:05:59 +02:00
return self . _store . get ( key , None )
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
def add ( self , key , value ) :
" Add new key and value "
2014-05-11 01:05:59 +02:00
self . _store [ key ] = value
2014-05-15 10:05:41 +02:00
self . obj . set_recache_protection ( )
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
def remove ( self , key ) :
" Remove key from storage "
2014-05-11 01:05:59 +02:00
if key in self . _store :
del self . _store [ key ]
2014-05-15 10:05:41 +02:00
self . obj . set_recache_protection ( self . _store )
2013-11-14 19:31:17 +01:00
2014-08-05 09:05:44 +02:00
def clear ( self ) :
" Remove all nattributes from handler "
self . _store = { }
2014-05-11 01:05:59 +02:00
def all ( self , return_tuples = False ) :
" List all keys or (keys, values) stored, except _keys "
if return_tuples :
return [ ( key , value ) for ( key , value ) in self . _store . items ( ) if not key . startswith ( " _ " ) ]
return [ key for key in self . _store if not key . startswith ( " _ " ) ]
2013-10-15 20:00:18 +02:00
2013-11-14 19:31:17 +01:00
2013-07-12 11:12:21 +02:00
#------------------------------------------------------------
#
# Tags
#
#------------------------------------------------------------
class Tag ( models . Model ) :
"""
Tags are quick markers for objects in - game . An typeobject
can have any number of tags , stored via its db_tags property .
Tagging similar objects will make it easier to quickly locate the
group later ( such as when implementing zones ) . The main advantage
of tagging as opposed to using Attributes is speed ; a tag is very
limited in what data it can hold , and the tag key + category is
indexed for efficient lookup in the database . Tags are shared between
objects - a new tag is only created if the key + category combination
did not previously exist , making them unsuitable for storing
2013-08-24 21:23:43 +02:00
object - related data ( for this a full Attribute
2013-07-12 11:12:21 +02:00
should be used ) .
The ' db_data ' field is intended as a documentation
field for the tag itself , such as to document what this tag + category
stands for and display that in a web interface or similar .
The main default use for Tags is to implement Aliases for objects .
this uses the ' aliases ' tag category , which is also checked by the
default search functions of Evennia to allow quick searches by alias .
"""
2013-11-14 19:31:17 +01:00
db_key = models . CharField ( ' key ' , max_length = 255 , null = True ,
help_text = " tag identifier " , db_index = True )
db_category = models . CharField ( ' category ' , max_length = 64 , null = True ,
help_text = " tag category " , db_index = True )
db_data = models . TextField ( ' data ' , null = True , blank = True ,
help_text = " optional data field with extra information. This is not searched for. " )
2014-10-25 22:40:38 +02:00
# this is "objectdb" etc. Required behind the scenes
2014-02-16 13:19:31 +01:00
db_model = models . CharField ( ' model ' , max_length = 32 , null = True , help_text = " database model to Tag " , db_index = True )
# this is None, alias or permission
db_tagtype = models . CharField ( ' tagtype ' , max_length = 16 , null = True , help_text = " overall type of Tag " , db_index = True )
2013-11-14 19:31:17 +01:00
2013-07-12 11:12:21 +02:00
class Meta :
" Define Django meta options "
verbose_name = " Tag "
2014-06-30 20:14:58 +02:00
unique_together = ( ( ' db_key ' , ' db_category ' , ' db_tagtype ' ) , )
index_together = ( ( ' db_key ' , ' db_category ' , ' db_tagtype ' ) , )
2013-11-14 19:31:17 +01:00
2013-07-12 11:12:21 +02:00
def __unicode__ ( self ) :
return u " %s " % self . db_key
2013-11-14 19:31:17 +01:00
2013-07-12 11:12:21 +02:00
def __str__ ( self ) :
return str ( self . db_key )
2010-08-29 18:46:58 +00:00
2013-07-12 14:44:49 +02:00
2011-04-23 11:54:08 +00:00
#
2013-08-24 21:23:43 +02:00
# Handlers making use of the Tags model
2011-04-23 11:54:08 +00:00
#
2013-07-12 14:44:49 +02:00
class TagHandler ( object ) :
2011-04-23 11:54:08 +00:00
"""
2013-07-12 14:44:49 +02:00
Generic tag - handler . Accessed via TypedObject . tags .
2011-04-23 11:54:08 +00:00
"""
2013-08-24 21:23:43 +02:00
_m2m_fieldname = " db_tags "
2014-02-16 13:19:31 +01:00
_tagtype = None
2013-08-24 21:23:43 +02:00
2014-02-16 13:19:31 +01:00
def __init__ ( self , obj ) :
2013-07-12 14:44:49 +02:00
"""
Tags are stored internally in the TypedObject . db_tags m2m field
2014-02-16 13:19:31 +01:00
with an tag . db_model based on the obj the taghandler is stored on
and with a tagtype given by self . handlertype
2013-07-12 14:44:49 +02:00
"""
self . obj = obj
2014-07-05 20:32:08 +02:00
self . _objid = obj . id
2014-07-06 16:03:27 +02:00
self . _model = obj . __class__ . __name__ . lower ( )
2013-10-20 21:02:37 +02:00
self . _cache = None
def _recache ( self ) :
2014-07-05 20:32:08 +02:00
" Cache all tags of this object "
query = { " %s __id " % self . _model : self . _objid ,
" tag__db_tagtype " : self . _tagtype }
tagobjs = [ conn . tag for conn in getattr ( self . obj , self . _m2m_fieldname ) . through . objects . filter ( * * query ) ]
self . _cache = dict ( ( " %s - %s " % ( to_str ( tagobj . db_key ) . lower ( ) ,
tagobj . db_category . lower ( ) if tagobj . db_category else None ) ,
tagobj ) for tagobj in tagobjs )
2013-07-12 14:44:49 +02:00
2014-07-01 00:46:20 +02:00
def add ( self , tag = None , category = None , data = None ) :
2013-08-24 21:23:43 +02:00
" Add a new tag to the handler. Tag is a string or a list of strings. "
2014-07-01 00:46:20 +02:00
if not tag :
return
2013-08-24 23:57:44 +02:00
for tagstr in make_iter ( tag ) :
2014-02-16 22:09:35 +01:00
if not tagstr :
continue
tagstr = tagstr . strip ( ) . lower ( )
category = category . strip ( ) . lower ( ) if category is not None else None
2013-11-14 19:31:17 +01:00
data = str ( data ) if data is not None else None
# this will only create tag if no matches existed beforehand (it
# will overload data on an existing tag since that is not
# considered part of making the tag unique)
2014-10-25 22:40:38 +02:00
tagobj = self . obj . __class__ . objects . create_tag ( key = tagstr , category = category , data = data ,
2014-07-05 20:32:08 +02:00
tagtype = self . _tagtype )
2014-05-10 17:12:49 +02:00
getattr ( self . obj , self . _m2m_fieldname ) . add ( tagobj )
2013-11-14 19:31:17 +01:00
if self . _cache is None :
2013-10-20 21:02:37 +02:00
self . _recache ( )
2014-02-16 13:19:31 +01:00
cachestring = " %s - %s " % ( tagstr , category )
self . _cache [ cachestring ] = tagobj
2013-08-24 21:23:43 +02:00
2014-02-16 13:19:31 +01:00
def get ( self , key , category = " " , return_tagobj = False ) :
2013-11-14 19:31:17 +01:00
"""
2013-11-28 14:11:18 +01:00
Get the tag for the given key or list of tags . If
return_data = True , return the matching Tag objects instead .
2014-02-16 13:19:31 +01:00
Returns a single tag if a unique match , otherwise a list
2013-11-14 19:31:17 +01:00
"""
if self . _cache is None or not _TYPECLASS_AGGRESSIVE_CACHE :
2013-10-20 21:02:37 +02:00
self . _recache ( )
2013-08-24 21:23:43 +02:00
ret = [ ]
2014-02-16 13:19:31 +01:00
category = category . strip ( ) . lower ( ) if category is not None else None
searchkey = [ " %s - %s " % ( key . strip ( ) . lower ( ) , category ) if key is not None else None for key in make_iter ( key ) ]
2014-03-31 07:31:48 +02:00
ret = [ val for val in ( self . _cache . get ( keystr ) for keystr in searchkey ) if val ]
2014-02-16 13:19:31 +01:00
ret = [ to_str ( tag . db_data ) for tag in ret ] if return_tagobj else ret
2013-11-14 19:31:17 +01:00
return ret [ 0 ] if len ( ret ) == 1 else ret
2013-07-12 14:44:49 +02:00
2014-02-16 13:19:31 +01:00
def remove ( self , key , category = None ) :
" Remove a tag from the handler based ond key and category. "
2014-03-31 07:31:48 +02:00
for key in make_iter ( key ) :
if not ( key or key . strip ( ) ) : # we don't allow empty tags
2013-08-24 21:23:43 +02:00
continue
2014-03-31 07:31:48 +02:00
tagstr = key . strip ( ) . lower ( )
category = category . strip ( ) . lower ( ) if category is not None else None
2013-11-14 19:31:17 +01:00
# This does not delete the tag object itself. Maybe it should do
# that when no objects reference the tag anymore (how to check)?
2013-10-20 21:02:37 +02:00
tagobj = self . obj . db_tags . filter ( db_key = tagstr , db_category = category )
2013-07-12 15:34:54 +02:00
if tagobj :
2014-05-10 17:12:49 +02:00
getattr ( self . obj , self . _m2m_fieldname ) . remove ( tagobj [ 0 ] )
2013-11-11 22:44:22 +01:00
self . _recache ( )
2013-10-20 21:02:37 +02:00
2013-07-12 15:57:46 +02:00
def clear ( self ) :
" Remove all tags from the handler "
2014-07-08 09:22:49 +02:00
getattr ( self . obj , self . _m2m_fieldname ) . clear ( )
2013-10-20 21:02:37 +02:00
self . _recache ( )
2013-07-12 14:44:49 +02:00
2013-12-09 09:13:51 +01:00
def all ( self , category = None , return_key_and_category = False ) :
"""
Get all tags in this handler .
If category is given , return only Tags with this category . If
return_keys_and_categories is set , return a list of tuples [ ( key , category ) , . . . ]
"""
2013-11-14 19:31:17 +01:00
if self . _cache is None or not _TYPECLASS_AGGRESSIVE_CACHE :
2013-10-20 21:02:37 +02:00
self . _recache ( )
2013-12-02 16:39:21 +01:00
if category :
2014-02-16 13:19:31 +01:00
category = category . strip ( ) . lower ( ) if category is not None else None
2014-07-05 20:32:08 +02:00
matches = [ tag for tag in self . _cache . values ( ) if tag . db_category == category ]
2013-12-09 09:13:51 +01:00
else :
2014-02-16 13:19:31 +01:00
matches = self . _cache . values ( )
2014-07-05 20:32:08 +02:00
2014-02-16 13:19:31 +01:00
if matches :
2014-07-07 22:47:26 +02:00
matches = sorted ( matches , key = lambda o : o . id )
2014-02-16 13:19:31 +01:00
if return_key_and_category :
# return tuple (key, category)
return [ ( to_str ( p . db_key ) , to_str ( p . db_category ) ) for p in matches ]
else :
return [ to_str ( p . db_key ) for p in matches ]
2014-02-16 16:55:39 +01:00
return [ ]
2013-12-09 09:13:51 +01:00
2013-07-12 14:44:49 +02:00
def __str__ ( self ) :
return " , " . join ( self . all ( ) )
2013-11-14 19:31:17 +01:00
2013-07-12 14:44:49 +02:00
def __unicode ( self ) :
return u " , " . join ( self . all ( ) )
2013-11-14 19:31:17 +01:00
2013-08-24 21:23:43 +02:00
class AliasHandler ( TagHandler ) :
2014-02-16 13:19:31 +01:00
_tagtype = " alias "
2011-04-23 11:54:08 +00:00
2013-11-14 19:31:17 +01:00
2013-08-24 21:23:43 +02:00
class PermissionHandler ( TagHandler ) :
2014-02-16 13:19:31 +01:00
_tagtype = " permission "
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
2014-12-20 19:04:49 +01:00
#
# Meta class for typeclasses
#
# django patch imports
import copy
from django . apps import apps
from django . db . models . base import subclass_exception
import warnings
from django . db . models . options import Options
from django . utils . deprecation import RemovedInDjango19Warning
2014-12-20 19:19:48 +01:00
from django . core . exceptions import MultipleObjectsReturned , FieldError
2014-12-20 19:04:49 +01:00
from django . apps . config import MODELS_MODULE_NAME
from django . db . models . fields . related import OneToOneField
#/ django patch imports
from src . utils . idmapper . base import SharedMemoryModelBase
class TypeclassBase ( SharedMemoryModelBase ) :
"""
Metaclass which should be set for the root of model proxies
that don ' t define any new fields, like Object, Script etc.
"""
def __new__ ( cls , name , bases , attrs ) :
"""
We must define our Typeclasses as proxies . We also store the path
directly on the class , this is useful for managers .
"""
# typeclass proxy setup
if " Meta " in attrs :
attrs [ " Meta " ] . proxy = True
else :
class Meta :
proxy = True
attrs [ " Meta " ] = Meta
# patch start - django multi-inheritance
# this is a copy of django.db.models.base.__new__
# with a few lines (marked patch below) changed
# as per https://code.djangoproject.com/ticket/11560
super_new = super ( ModelBase , cls ) . __new__
# Also ensure initialization is only performed for subclasses of Model
# (excluding Model class itself).
parents = [ b for b in bases if isinstance ( b , ModelBase ) ]
if not parents :
return super_new ( cls , name , bases , attrs )
# Create the class.
module = attrs . pop ( ' __module__ ' )
new_class = super_new ( cls , name , bases , { ' __module__ ' : module } )
attr_meta = attrs . pop ( ' Meta ' , None )
abstract = getattr ( attr_meta , ' abstract ' , False )
if not attr_meta :
meta = getattr ( new_class , ' Meta ' , None )
else :
meta = attr_meta
base_meta = getattr ( new_class , ' _meta ' , None )
# Look for an application configuration to attach the model to.
app_config = apps . get_containing_app_config ( module )
if getattr ( meta , ' app_label ' , None ) is None :
if app_config is None :
# If the model is imported before the configuration for its
# application is created (#21719), or isn't in an installed
# application (#21680), use the legacy logic to figure out the
# app_label by looking one level up from the package or module
# named 'models'. If no such package or module exists, fall
# back to looking one level up from the module this model is
# defined in.
# For 'django.contrib.sites.models', this would be 'sites'.
# For 'geo.models.places' this would be 'geo'.
msg = (
" Model class %s . %s doesn ' t declare an explicit app_label "
" and either isn ' t in an application in INSTALLED_APPS or "
" else was imported before its application was loaded. " %
( module , name ) )
if abstract :
msg + = " Its app_label will be set to None in Django 1.9. "
else :
msg + = " This will no longer be supported in Django 1.9. "
warnings . warn ( msg , RemovedInDjango19Warning , stacklevel = 2 )
model_module = sys . modules [ new_class . __module__ ]
package_components = model_module . __name__ . split ( ' . ' )
package_components . reverse ( ) # find the last occurrence of 'models'
try :
app_label_index = package_components . index ( MODELS_MODULE_NAME ) + 1
except ValueError :
app_label_index = 1
kwargs = { " app_label " : package_components [ app_label_index ] }
else :
kwargs = { " app_label " : app_config . label }
else :
kwargs = { }
new_class . add_to_class ( ' _meta ' , Options ( meta , * * kwargs ) )
if not abstract :
new_class . add_to_class (
' DoesNotExist ' ,
subclass_exception (
str ( ' DoesNotExist ' ) ,
tuple ( x . DoesNotExist for x in parents if hasattr ( x , ' _meta ' ) and not x . _meta . abstract ) or ( ObjectDoesNotExist , ) ,
module ,
attached_to = new_class ) )
new_class . add_to_class (
' MultipleObjectsReturned ' ,
subclass_exception (
str ( ' MultipleObjectsReturned ' ) ,
tuple ( x . MultipleObjectsReturned for x in parents if hasattr ( x , ' _meta ' ) and not x . _meta . abstract ) or ( MultipleObjectsReturned , ) ,
module ,
attached_to = new_class ) )
if base_meta and not base_meta . abstract :
# Non-abstract child classes inherit some attributes from their
# non-abstract parent (unless an ABC comes before it in the
# method resolution order).
if not hasattr ( meta , ' ordering ' ) :
new_class . _meta . ordering = base_meta . ordering
if not hasattr ( meta , ' get_latest_by ' ) :
new_class . _meta . get_latest_by = base_meta . get_latest_by
is_proxy = new_class . _meta . proxy
# If the model is a proxy, ensure that the base class
# hasn't been swapped out.
if is_proxy and base_meta and base_meta . swapped :
raise TypeError ( " %s cannot proxy the swapped model ' %s ' . " % ( name , base_meta . swapped ) )
if getattr ( new_class , ' _default_manager ' , None ) :
if not is_proxy :
# Multi-table inheritance doesn't inherit default manager from
# parents.
new_class . _default_manager = None
new_class . _base_manager = None
else :
# Proxy classes do inherit parent's default manager, if none is
# set explicitly.
new_class . _default_manager = new_class . _default_manager . _copy_to_model ( new_class )
new_class . _base_manager = new_class . _base_manager . _copy_to_model ( new_class )
# Add all attributes to the class.
for obj_name , obj in attrs . items ( ) :
new_class . add_to_class ( obj_name , obj )
# All the fields of any type declared on this model
new_fields = (
new_class . _meta . local_fields +
new_class . _meta . local_many_to_many +
new_class . _meta . virtual_fields
)
field_names = set ( f . name for f in new_fields )
# Basic setup for proxy models.
if is_proxy :
base = None
for parent in [ kls for kls in parents if hasattr ( kls , ' _meta ' ) ] :
if parent . _meta . abstract :
if parent . _meta . fields :
raise TypeError ( " Abstract base class containing model fields not permitted for proxy model ' %s ' . " % name )
else :
continue
#if base is not None: # patch
while parent . _meta . proxy : # patch
parent = parent . _meta . proxy_for_model # patch
if base is not None and base is not parent : # patch
raise TypeError ( " Proxy model ' %s ' has more than one non-abstract model base class. " % name )
else :
base = parent
if base is None :
raise TypeError ( " Proxy model ' %s ' has no non-abstract model base class. " % name )
new_class . _meta . setup_proxy ( base )
new_class . _meta . concrete_model = base . _meta . concrete_model
else :
new_class . _meta . concrete_model = new_class
# Collect the parent links for multi-table inheritance.
parent_links = { }
for base in reversed ( [ new_class ] + parents ) :
# Conceptually equivalent to `if base is Model`.
if not hasattr ( base , ' _meta ' ) :
continue
# Skip concrete parent classes.
if base != new_class and not base . _meta . abstract :
continue
# Locate OneToOneField instances.
for field in base . _meta . local_fields :
if isinstance ( field , OneToOneField ) :
parent_links [ field . rel . to ] = field
# Do the appropriate setup for any model parents.
for base in parents :
original_base = base
if not hasattr ( base , ' _meta ' ) :
# Things without _meta aren't functional models, so they're
# uninteresting parents.
continue
parent_fields = base . _meta . local_fields + base . _meta . local_many_to_many
# Check for clashes between locally declared fields and those
# on the base classes (we cannot handle shadowed fields at the
# moment).
for field in parent_fields :
if field . name in field_names :
raise FieldError (
' Local field %r in class %r clashes '
' with field of similar name from '
' base class %r ' % ( field . name , name , base . __name__ )
)
if not base . _meta . abstract :
# Concrete classes...
base = base . _meta . concrete_model
if base in parent_links :
field = parent_links [ base ]
elif not is_proxy :
attr_name = ' %s _ptr ' % base . _meta . model_name
field = OneToOneField ( base , name = attr_name ,
auto_created = True , parent_link = True )
# Only add the ptr field if it's not already present;
# e.g. migrations will already have it specified
if not hasattr ( new_class , attr_name ) :
new_class . add_to_class ( attr_name , field )
else :
field = None
new_class . _meta . parents [ base ] = field
else :
# .. and abstract ones.
for field in parent_fields :
new_class . add_to_class ( field . name , copy . deepcopy ( field ) )
# Pass any non-abstract parent classes onto child.
new_class . _meta . parents . update ( base . _meta . parents )
# Inherit managers from the abstract base classes.
new_class . copy_managers ( base . _meta . abstract_managers )
# Proxy models inherit the non-abstract managers from their base,
# unless they have redefined any of them.
if is_proxy :
new_class . copy_managers ( original_base . _meta . concrete_managers )
# Inherit virtual fields (like GenericForeignKey) from the parent
# class
for field in base . _meta . virtual_fields :
if base . _meta . abstract and field . name in field_names :
raise FieldError (
' Local field %r in class %r clashes '
' with field of similar name from '
' abstract base class %r ' % ( field . name , name , base . __name__ )
)
new_class . add_to_class ( field . name , copy . deepcopy ( field ) )
if abstract :
# Abstract base models can't be instantiated and don't appear in
# the list of models for an app. We do the final setup for them a
# little differently from normal models.
attr_meta . abstract = False
new_class . Meta = attr_meta
return new_class
new_class . _prepare ( )
new_class . _meta . apps . register_model ( new_class . _meta . app_label , new_class )
return new_class
# /patch end
#
# Main TypedObject abstraction
#
2014-12-19 16:29:41 +01: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
#
#
2013-11-14 19:31:17 +01:00
# These databse fields are all accessed and set using their corresponding
# properties, named same as the field, but without the db_* prefix
# (no separate save() call is needed)
2012-03-30 23:47:22 +02:00
2013-11-14 19:31:17 +01:00
# Main identifier of the object, for searching. Is accessed with self.key
# or self.name
2012-02-06 13:18:25 +01:00
db_key = models . CharField ( ' key ' , max_length = 255 , db_index = True )
2013-11-14 19:31:17 +01:00
# This is the python path to the type class this object is tied to the
# typeclass is what defines what kind of Object this is)
2013-07-11 15:59: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. " )
# Creation date. This is not changed once the object is created.
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)
2013-08-24 23:57:44 +02:00
#db_permissions = models.CharField('permissions', max_length=255, blank=True,
2013-11-14 19:31:17 +01:00
# help_text="a comma-separated list of text strings checked by
# in-game locks. They are often used for hierarchies, such as letting a Player have permission 'Wizards', 'Builders' etc. Character objects use 'Players' by default. Most other objects don't have any permissions.")
2012-03-30 23:47:22 +02:00
# Lock storage
2013-07-11 15:59:03 +02:00
db_lock_storage = models . TextField ( ' locks ' , blank = True ,
help_text = " locks limit access to an entity. A lock is defined as a ' lock string ' on the form ' type:lockfunctions ' , defining what functionality is locked and how to determine access. Not defining a lock means no access is granted. " )
2013-07-12 11:12:21 +02:00
# many2many relationships
2013-07-11 15:59:03 +02:00
db_attributes = models . ManyToManyField ( Attribute , null = True ,
2013-07-21 14:18:36 -05:00
help_text = ' attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases). ' )
2013-07-12 11:12:21 +02:00
db_tags = models . ManyToManyField ( Tag , null = True ,
help_text = ' tags on this object. Tags are simple string markers to identify, group and alias objects. ' )
2010-08-29 18:46:58 +00:00
# Database manager
objects = managers . TypedObjectManager ( )
2013-07-11 15:59:03 +02:00
# quick on-object typeclass cache for speed
2012-03-30 23:47:22 +02:00
_cached_typeclass = None
2011-08-06 18:15:04 +00:00
2014-12-19 16:29:41 +01:00
# typeclass mechanism
def _import_class ( self , path ) :
path , clsname = path . rsplit ( " . " , 1 )
mod = import_module ( path )
return getattr ( mod , clsname )
2011-03-15 16:08:32 +00:00
def __init__ ( self , * args , * * kwargs ) :
2014-12-19 16:29:41 +01:00
typeclass_path = kwargs . pop ( " typeclass " , None )
2014-05-15 09:51:24 +02:00
super ( TypedObject , self ) . __init__ ( * args , * * kwargs )
2014-12-19 16:29:41 +01:00
if typeclass_path :
self . __class__ = self . _import_class ( typeclass_path )
self . db_typclass_path = typeclass_path
elif self . db_typeclass_path :
self . __class__ = self . _import_class ( self . db_typeclass_path )
else :
self . db_typeclass_path = " %s . %s " % ( self . __module__ , self . __class__ . __name__ )
2014-07-06 13:10:03 +02:00
# initialize all handlers in a lazy fashion
@lazy_property
def attributes ( self ) :
return AttributeHandler ( self )
@lazy_property
def locks ( self ) :
return LockHandler ( self )
@lazy_property
def tags ( self ) :
return TagHandler ( self )
@lazy_property
def aliases ( self ) :
return AliasHandler ( self )
@lazy_property
def permissions ( self ) :
return PermissionHandler ( self )
@lazy_property
def nattributes ( self ) :
return NAttributeHandler ( 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
2013-07-11 15:59:03 +02:00
# wrapper
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).
2013-07-11 15:59:03 +02:00
# name property (alias to self.key)
2013-11-14 19:31:17 +01:00
def __name_get ( self ) :
return self . key
def __name_set ( self , value ) :
self . key = value
def __name_del ( self ) :
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
#
#
2012-03-30 23:47:22 +02:00
# TypedObject main class methods and properties
2010-08-29 18:46:58 +00:00
#
#
2012-03-31 13:06:29 +02:00
_typeclass_paths = settings . OBJECT_TYPECLASS_PATHS
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 ) :
2013-07-12 22:08:15 +02:00
return smart_str ( " %s " % _GA ( self , " db_key " ) )
2010-08-29 18:46:58 +00:00
def __unicode__ ( self ) :
2013-07-12 22:08:15 +02:00
return u " %s " % _GA ( self , " db_key " )
2010-08-29 18:46:58 +00:00
2012-04-26 17:47:25 +02:00
#@property
def __dbid_get ( self ) :
"""
Caches and returns the unique id of the object .
Use this instead of self . id , which is not cached .
"""
2012-11-01 11:20:07 +01:00
dbid = get_prop_cache ( self , " _dbid " )
2012-04-26 17:47:25 +02:00
if not dbid :
dbid = _GA ( self , " id " )
2012-11-01 11:20:07 +01:00
set_prop_cache ( self , " _dbid " , dbid )
2012-04-26 17:47:25 +02:00
return dbid
2013-11-14 19:31:17 +01:00
2012-04-26 17:47:25 +02:00
def __dbid_set ( self , value ) :
raise Exception ( " dbid cannot be set! " )
2013-11-14 19:31:17 +01:00
2012-04-26 17:47:25 +02:00
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 " ) ( )
2013-11-14 19:31:17 +01:00
2012-04-26 17:47:25 +02:00
def __dbref_set ( self ) :
raise Exception ( " dbref cannot be set! " )
2013-11-14 19:31:17 +01:00
2012-04-26 17:47:25 +02:00
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
#
# Object manipulation methods
2012-03-30 23:47:22 +02:00
#
2010-08-29 18:46:58 +00:00
2014-04-21 11:52:48 +02:00
def swap_typeclass ( self , new_typeclass , clean_attributes = False ,
run_start_hooks = True , 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
2013-05-11 16:09:26 +02:00
cls = new_typeclass
2010-08-29 18:46:58 +00:00
new_typeclass = " %s . %s " % ( cls . __module__ , cls . __name__ )
2012-11-13 21:34:42 +01:00
else :
new_typeclass = " %s " % to_str ( new_typeclass )
2010-08-29 18:46:58 +00:00
# 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
2014-04-21 11:52:48 +02:00
if inherits_from ( self , " src.scripts.models.ScriptDB " ) :
if self . interval > 0 :
raise RuntimeError ( " Cannot use swap_typeclass on time-dependent " \
" Script ' %s ' . \n Stop and start a new Script of the " \
" right type instead. " % self . key )
2012-11-13 21:34:42 +01:00
_SA ( 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
2012-11-13 21:34:42 +01:00
if self . typeclass_path != new_typeclass . path and no_default :
2011-10-03 23:53:23 +02:00
# 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 )
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 :
2014-08-04 12:15:10 +02:00
self . attributes . remove ( attr )
2010-08-29 18:46:58 +00:00
for nattr in clean_attributes :
if hasattr ( self . ndb , nattr ) :
2014-08-04 12:15:10 +02:00
self . nattributes . remove ( nattr )
2010-08-29 18:46:58 +00:00
else :
#print "deleting attrs ..."
2014-08-04 12:15:10 +02:00
self . attributes . clear ( )
self . nattributes . clear ( )
2010-08-29 18:46:58 +00:00
2014-04-21 11:52:48 +02:00
if run_start_hooks :
# run hooks for this new typeclass
if inherits_from ( self , " src.objects.models.ObjectDB " ) :
new_typeclass . basetype_setup ( )
new_typeclass . at_object_creation ( )
elif inherits_from ( self , " src.players.models.PlayerDB " ) :
new_typeclass . basetype_setup ( )
new_typeclass . at_player_creation ( )
elif inherits_from ( self , " src.scripts.models.ScriptDB " ) :
new_typeclass . at_script_creation ( )
new_typeclass . start ( )
elif inherits_from ( self , " src.channels.models.Channel " ) :
# channels do no initial setup
pass
2012-03-30 23:47:22 +02:00
return True
2010-08-29 18:46:58 +00:00
#
2013-08-24 23:57:44 +02:00
# Lock / permission methods
#
2014-01-18 23:56:07 +01:00
def access ( self , accessing_obj , access_type = ' read ' , default = False , * * kwargs ) :
2013-08-24 23:57:44 +02:00
"""
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
2014-01-18 23:56:07 +01:00
* * kwargs - this is ignored , but is there to make the api consistent with the
object - typeclass method access , which use it to feed to its hook methods .
2013-08-24 23:57:44 +02:00
"""
return self . locks . check ( accessing_obj , access_type = access_type , default = default )
def check_permstring ( self , permstring ) :
"""
2013-11-14 19:31:17 +01:00
This explicitly checks if we hold particular permission without
2014-01-18 23:56:07 +01:00
involving any locks . It does - not - trigger the at_access hook .
2013-08-24 23:57:44 +02:00
"""
if hasattr ( self , " player " ) :
2013-11-14 19:31:17 +01:00
if self . player and self . player . is_superuser :
return True
2013-08-24 23:57:44 +02:00
else :
2013-11-14 19:31:17 +01:00
if self . is_superuser :
return True
2013-08-24 23:57:44 +02:00
if not permstring :
return False
perm = permstring . lower ( )
perms = [ p . lower ( ) for p in self . permissions . all ( ) ]
if perm in perms :
# 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 perms and hpos > ppos )
return False
2014-06-15 12:27:48 +02:00
#
# Deletion methods
#
def _deleted ( self , * args , * * kwargs ) :
" Scrambling method for already deleted objects "
raise ObjectDoesNotExist ( " This object was already deleted! " )
_is_deleted = False # this is checked by db_* wrappers
2014-04-20 16:47:03 +02:00
def delete ( self ) :
" Cleaning up handlers on the typeclass level "
2014-06-14 19:31:19 +02:00
global TICKER_HANDLER
if not TICKER_HANDLER :
from src . scripts . tickerhandler import TICKER_HANDLER
2014-06-15 12:27:48 +02:00
TICKER_HANDLER . remove ( self ) # removes objects' all ticker subscriptions
2014-07-06 13:10:03 +02:00
_GA ( self , " permissions " ) . clear ( )
_GA ( self , " attributes " ) . clear ( )
_GA ( self , " aliases " ) . clear ( )
if hasattr ( self , " nicks " ) :
2014-06-15 12:27:48 +02:00
_GA ( self , " nicks " ) . clear ( )
2014-05-05 00:07:04 +02:00
_SA ( self , " _cached_typeclass " , None )
_GA ( self , " flush_from_cache " ) ( )
2014-06-15 12:27:48 +02:00
# scrambling properties
self . delete = self . _deleted
self . _is_deleted = True
2014-04-20 16:47:03 +02:00
super ( TypedObject , self ) . delete ( )
2013-10-15 20:00:18 +02:00
#
# Memory management
#
2013-08-24 23:57:44 +02:00
def flush_from_cache ( self ) :
"""
2013-11-14 19:31:17 +01:00
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 .
2013-08-24 23:57:44 +02:00
"""
self . __class__ . flush_cached_instance ( self )
2013-10-15 20:00:18 +02:00
#
# Attribute storage
#
#@property db
def __db_get ( self ) :
"""
Attribute handler wrapper . Allows for the syntax
obj . db . attrname = value
and
value = obj . db . attrname
and
del obj . db . attrname
and
2013-11-14 19:31:17 +01:00
all_attr = obj . db . all ( ) ( unless there is an attribute
named ' all ' , in which case that will be returned instead ) .
2013-10-15 20:00:18 +02:00
"""
try :
return self . _db_holder
except AttributeError :
class DbHolder ( object ) :
" Holder for allowing property access of attributes "
def __init__ ( self , obj ) :
2014-05-11 01:05:59 +02:00
_SA ( self , " attrhandler " , _GA ( obj , " attributes " ) )
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
def __getattribute__ ( self , attrname ) :
if attrname == ' all ' :
# we allow to overload our default .all
attr = _GA ( self , " attrhandler " ) . get ( " all " )
if attr :
return attr
return _GA ( self , ' all ' )
return _GA ( self , " attrhandler " ) . get ( attrname )
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
def __setattr__ ( self , attrname , value ) :
_GA ( self , " attrhandler " ) . add ( attrname , value )
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
def __delattr__ ( self , attrname ) :
_GA ( self , " attrhandler " ) . remove ( attrname )
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
def get_all ( self ) :
return _GA ( self , " attrhandler " ) . all ( )
all = property ( get_all )
self . _db_holder = DbHolder ( self )
return self . _db_holder
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
#@db.setter
def __db_set ( self , value ) :
" Stop accidentally replacing the db object "
string = " Cannot assign directly to db object! "
string + = " Use db.attr=value instead. "
raise Exception ( string )
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
#@db.deleter
def __db_del ( self ) :
" Stop accidental deletion. "
raise Exception ( " Cannot delete the db object! " )
db = property ( __db_get , __db_set , __db_del )
2013-08-24 23:57:44 +02:00
#
# Non-persistent (ndb) storage
#
#@property ndb
def __ndb_get ( self ) :
"""
A non - attr_obj store ( ndb : NonDataBase ) . Everything stored
to this is guaranteed to be cleared when a server is shutdown .
Syntax is same as for the _get_db_holder ( ) method and
property , e . g . obj . ndb . attr = value etc .
"""
try :
return self . _ndb_holder
except AttributeError :
2014-05-11 01:05:59 +02:00
class NDbHolder ( object ) :
" Holder for allowing property access of attributes "
def __init__ ( self , obj ) :
_SA ( self , " nattrhandler " , _GA ( obj , " nattributes " ) )
2013-11-14 19:31:17 +01:00
2014-05-11 01:05:59 +02:00
def __getattribute__ ( self , attrname ) :
if attrname == ' all ' :
# we allow to overload our default .all
attr = _GA ( self , " nattrhandler " ) . get ( " all " )
if attr :
return attr
return _GA ( self , ' all ' )
return _GA ( self , " nattrhandler " ) . get ( attrname )
def __setattr__ ( self , attrname , value ) :
_GA ( self , " nattrhandler " ) . add ( attrname , value )
2013-11-14 19:31:17 +01:00
2014-05-11 01:05:59 +02:00
def __delattr__ ( self , attrname ) :
_GA ( self , " nattrhandler " ) . remove ( attrname )
def get_all ( self ) :
return _GA ( self , " nattrhandler " ) . all ( )
all = property ( get_all )
self . _ndb_holder = NDbHolder ( self )
2013-08-24 23:57:44 +02:00
return self . _ndb_holder
2013-11-14 19:31:17 +01:00
2014-05-11 01:05:59 +02:00
#@db.setter
2013-08-24 23:57:44 +02:00
def __ndb_set ( self , value ) :
2014-05-11 01:05:59 +02:00
" Stop accidentally replacing the ndb object "
2013-08-24 23:57:44 +02:00
string = " Cannot assign directly to ndb object! "
2014-05-11 01:05:59 +02:00
string + = " Use ndb.attr=value instead. "
2013-08-24 23:57:44 +02:00
raise Exception ( string )
2013-11-14 19:31:17 +01:00
2014-05-11 01:05:59 +02:00
#@db.deleter
2013-08-24 23:57:44 +02:00
def __ndb_del ( self ) :
" Stop accidental deletion. "
raise Exception ( " Cannot delete the ndb object! " )
ndb = property ( __ndb_get , __ndb_set , __ndb_del )