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
2013-04-13 18:13:15 +02:00
#try:
# import cPickle as pickle
#except ImportError:
# import pickle
2010-08-29 18:46:58 +00:00
import traceback
2013-04-13 18:13:15 +02:00
#from collections import defaultdict
2012-04-28 00:37:36 +02:00
2013-07-08 18:13:21 +02:00
from django . db import models
2011-02-28 23:43:14 +00:00
from django . conf import settings
2010-08-29 18:46:58 +00:00
from django . utils . encoding import smart_str
2011-04-05 23:28:40 +00:00
from django . contrib . contenttypes . models import ContentType
2013-08-24 21:23:43 +02:00
2010-08-29 18:46:58 +00: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
2013-07-08 18:13:21 +02:00
2013-05-29 16:16:28 +02:00
#from src.server.caches import call_ndb_hooks
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
2013-10-20 21:02:37 +02:00
from src . utils import logger
2013-07-12 11:12:21 +02:00
from src . utils . utils import make_iter , is_iter , to_str
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 " )
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
_CTYPEGET = ContentType . objects . get
_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
#
#------------------------------------------------------------
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
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 )
2012-03-30 23:47:22 +02:00
# access through the value property
2013-07-08 18:13:21 +02:00
db_value = PickledObjectField ( ' value ' , null = True )
2013-08-24 21:23:43 +02:00
# string-specific storage for quick look-up
db_strvalue = models . TextField ( ' strvalue ' , null = True , blank = True )
# optional categorization of attribute
db_category = models . CharField ( ' category ' , max_length = 128 , db_index = True , blank = True , null = True )
2012-03-30 23:47:22 +02:00
# Lock storage
2013-02-03 09:54:36 -06:00
db_lock_storage = models . TextField ( ' locks ' , blank = True )
2010-08-29 18:46:58 +00:00
# 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
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).
# 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
as storing a dbobj which is then deleted elswhere ) out - of - sync .
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 )
#if self.no_cache:
# # re-create data from database and cache it
# value = from_pickle(self.db_value, db_obj=self)
# self.cached_value = value
# self.no_cache = False
#return self.cached_value
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
"""
Setter . Allows for self . value = value . We make sure to cache everything .
2012-03-30 23:47:22 +02:00
"""
2013-11-12 19:32:24 +01:00
self . db_value = to_pickle ( new_value )
2010-08-29 18:46:58 +00:00
self . save ( )
2013-09-14 23:18:36 +02:00
try :
self . _track_db_value_change . update ( self . cached_value )
except AttributeError :
pass
2013-11-12 19:32:24 +01:00
return
#to_store = to_pickle(new_value)
#self.cached_value = from_pickle(to_store, db_obj=self)
#self.no_cache = False
#self.db_value = to_store
#self.save()
#try:
# self._track_db_value_change.update(self.cached_value)
#except AttributeError:
# pass
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
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 )
2012-12-08 20:26:44 +01:00
def at_set ( self , new_value ) :
"""
Hook method called when the attribute changes value .
"""
pass
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 "
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
2013-10-20 21:02:37 +02:00
self . _cache = None
def _recache ( self ) :
self . _cache = dict ( ( " %s _ %s " % ( to_str ( attr . db_key ) . lower ( ) , to_str ( attr . db_category , force_string = True ) . lower ( ) ) , attr )
for attr in _GA ( self . obj , self . _m2m_fieldname ) . all ( ) )
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 ( )
catkey = to_str ( category , force_string = True ) . lower ( )
searchkeys = [ " %s _ %s " % ( k . lower ( ) , catkey ) for k in make_iter ( key ) ]
ret = [ self . _cache [ skey ] for skey in searchkeys if skey in self . _cache ]
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 ,
default_access = True ) :
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
instead . Returns None if no matches ( or [ ] if key was a list
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 .
"""
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 = [ ]
2013-10-21 22:51:16 +02:00
catkey = to_str ( category , force_string = True ) . lower ( )
if not key :
# return all with matching category (or no category)
catkey = " _ %s " % catkey
ret = [ attr for key , attr in self . _cache . items ( ) if key . endswith ( catkey ) ]
else :
for keystr in ( " %s _ %s " % ( k . lower ( ) , catkey ) for k in make_iter ( key ) ) :
attr_obj = self . _cache . get ( keystr )
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 :
ret . append ( default )
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 :
ret = ret if return_obj else [ attr . strvalue if attr else None for attr in ret ]
else :
ret = ret if return_obj else [ attr . value if attr else None for attr in ret ]
return ret [ 0 ] if len ( ret ) == 1 else ret
2013-07-12 11:12:21 +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 .
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 ( )
cachekey = " %s _ %s " % ( key . lower ( ) , to_str ( category , force_string = True ) . lower ( ) )
attr_obj = self . _cache . get ( cachekey )
2013-08-24 21:23:43 +02:00
if not attr_obj :
2013-10-20 21:02:37 +02:00
# no old attr available; create new.
attr_obj = Attribute ( db_key = key , db_category = category )
2013-11-14 19:31:17 +01:00
attr_obj . save ( ) # important
2013-10-20 21:02:37 +02:00
_GA ( self . obj , self . _m2m_fieldname ) . add ( attr_obj )
self . _cache [ cachekey ] = attr_obj
2013-08-24 21:23:43 +02:00
if lockstring :
attr_obj . locks . add ( lockstring )
2013-11-14 19:31:17 +01:00
# we shouldn't need to fear stale objects, the field signalling
# should catch all cases
2013-08-24 21:23:43 +02:00
if strattr :
# store as a simple string
attr_obj . strvalue = value
2013-10-20 21:02:37 +02:00
attr_obj . value = None
2013-08-24 21:23:43 +02:00
else :
# pickle arbitrary data
attr_obj . value = value
2013-10-20 21:02:37 +02:00
attr_obj . strvalue = None
2013-08-24 21:23:43 +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 ( )
catkey = to_str ( category , force_string = True ) . lower ( )
for keystr in ( " %s _ %s " % ( k . lower ( ) , catkey ) for k in make_iter ( key ) ) :
attr_obj = self . _cache . get ( keystr )
if attr_obj :
2013-11-14 19:31:17 +01:00
if accessing_obj and not attr_obj . access ( accessing_obj ,
self . _attredit , default = default_access ) :
2013-08-24 21:23:43 +02:00
continue
2013-10-20 21:02:37 +02:00
attr_obj . delete ( )
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 .
"""
2013-10-20 21:02:37 +02:00
for attr in self . all ( category = category , accessing_obj = accessing_obj , default_access = default_access ) :
2013-08-24 21:23:43 +02:00
if accessing_obj and not attr . access ( accessing_obj , self . _attredit , default = default_access ) :
continue
attr . delete ( )
2013-10-20 21:02:37 +02:00
self . _recache ( )
2013-08-24 21:23:43 +02:00
2013-08-25 19:15:27 +02:00
def all ( self , category = None , 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 ( )
catkey = " _ %s " % to_str ( category , force_string = True ) . lower ( )
return [ attr for key , attr in self . _cache . items ( ) if key . endswith ( catkey ) ]
#if category==None:
# all_attrs = _GA(self.obj, self._m2m_fieldname).all()
#else:
# all_attrs = _GA(self.obj, self._m2m_fieldname).filter(db_category=category)
#if accessing_obj:
# return [attr for attr in all_attrs if attr.access(accessing_obj, self._attrread, default=default_access)]
#else:
# return list(all_attrs)
2013-08-24 21:23:43 +02:00
2013-11-14 19:31:17 +01: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
"""
def has ( self , key , category = " inputline " ) :
2013-10-15 20:00:18 +02:00
category = " nick_ %s " % category
2013-08-24 21:23:43 +02:00
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 "
category = " nick_ %s " % 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 "
category = " nick_ %s " % category
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 "
category = " nick_ %s " % category
super ( NickHandler , self ) . remove ( key , category = category , * * kwargs )
2013-07-12 11:12:21 +02:00
2013-09-22 22:34:22 +02:00
def all ( self , category = None ) :
" Return all attributes with nick_* category "
if category :
category = " nick_ %s " % category
return super ( NickHandler , self ) . all ( category = category )
return _GA ( self . obj , self . _m2m_fieldname ) . filter ( db_category__startswith = " nick_ " )
2013-07-12 11:12:21 +02:00
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
class NAttributeHandler ( object ) :
"""
2013-11-14 19:31:17 +01:00
This stand - alone handler manages non - database saved properties by storing
them as properties on obj . ndb . It has the same methods as AttributeHandler ,
but they are much simplified .
2013-10-15 20:00:18 +02:00
"""
def __init__ ( self , obj ) :
" initialized on the object "
self . ndb = _GA ( obj , " ndb " )
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 "
2013-11-14 19:31:17 +01:00
return _GA ( self . ndb , key ) # ndb returns None if not found
2013-10-15 20:00:18 +02:00
def get ( self , key ) :
" Returns named key value "
return _GA ( self . ndb , key )
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 "
_SA ( self . ndb , key , value )
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
def remove ( self , key ) :
" Remove key from storage "
_DA ( self . ndb , key )
2013-11-14 19:31:17 +01:00
2013-10-15 20:00:18 +02:00
def all ( self ) :
" List all keys stored "
if callable ( self . ndb . all ) :
return self . ndb . all ( )
else :
return [ val for val in self . ndb . __dict__ . keys ( ) if not val . startswith ( ' _ ' ) ]
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. " )
2013-07-12 11:12:21 +02:00
objects = managers . TagManager ( )
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 "
2013-11-14 19:31:17 +01:00
unique_together = ( ( ' db_key ' , ' db_category ' ) , )
2013-07-12 12:04:57 +02:00
index_together = ( ( ' db_key ' , ' db_category ' ) , )
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 "
_base_category = " "
2013-07-12 14:44:49 +02:00
def __init__ ( self , obj , category_prefix = " " ) :
"""
Tags are stored internally in the TypedObject . db_tags m2m field
using the category < category_prefix > < tag_category >
"""
self . obj = obj
2013-11-14 19:31:17 +01:00
self . prefix = " %s %s " % ( category_prefix . strip ( ) . lower ( )
if category_prefix else " " , self . _base_category )
2013-10-20 21:02:37 +02:00
self . _cache = None
def _recache ( self ) :
2013-11-11 22:44:22 +01:00
self . _cache = dict ( ( to_str ( p . db_key ) , p ) for p in _GA ( self . obj , self . _m2m_fieldname ) . filter (
db_category__startswith = self . prefix ) )
2013-07-12 14:44:49 +02:00
def add ( self , tag , 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. "
2013-08-24 23:57:44 +02:00
for tagstr in make_iter ( tag ) :
2013-11-14 19:31:17 +01:00
tagstr = tagstr . strip ( ) . lower ( ) if tagstr is not None else None
category = " %s %s " % ( self . prefix , category . strip ( ) . lower ( ) if category is not None else " " )
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)
2013-08-24 23:57:44 +02:00
tagobj = Tag . objects . create_tag ( key = tagstr , category = category , data = data )
2013-08-24 21:23:43 +02:00
_GA ( 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 ( )
self . _cache [ tagstr ] = True
2013-08-24 21:23:43 +02:00
def get ( self , key , category = " " , return_obj = False ) :
2013-11-14 19:31:17 +01:00
"""
Get the data field for the given tag or list of tags . If
return_obj = True , return the matching Tag objects instead .
"""
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 = [ ]
2013-11-14 19:31:17 +01:00
category = " %s %s " % ( self . prefix , category . strip ( ) . lower ( )
if category is not None else " " )
ret = [ val for val in ( self . _cache . get ( keystr . strip ( ) . lower ( ) )
for keystr in make_iter ( key ) ) if val ]
2013-11-11 22:44:22 +01:00
ret = ret if return_obj else [ to_str ( tag . db_data ) for tag in ret if tag ]
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
def remove ( self , tag , category = None ) :
" Remove a tag from the handler "
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-07-12 15:34:54 +02:00
for tag in make_iter ( tag ) :
2013-11-14 19:31:17 +01:00
if not ( tag or tag . strip ( ) ) : # we don't allow empty tags
2013-08-24 21:23:43 +02:00
continue
2013-11-14 19:31:17 +01:00
tagstr = tag . strip ( ) . lower ( ) if tag is not None else None
category = " %s %s " % ( self . prefix , category . strip ( ) . lower ( )
if category is not None else " " )
# 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 :
2013-08-24 21:23:43 +02:00
_GA ( 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 "
2013-10-18 17:17:42 +02:00
_GA ( self . obj , self . _m2m_fieldname ) . filter ( db_category__startswith = self . prefix ) . clear ( )
2013-10-20 21:02:37 +02:00
self . _recache ( )
2013-07-12 14:44:49 +02:00
def all ( self ) :
" Get all tags in this handler "
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 ( )
return self . _cache . keys ( )
#return [to_str(p[0]) for p in _GA(self.obj, self._m2m_fieldname).filter(db_category__startswith=self.prefix).values_list("db_key") if p[0]]
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 ) :
_base_category = " 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 ) :
_base_category = " 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
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
2011-03-15 16:08:32 +00:00
# lock handler self.locks
def __init__ ( self , * args , * * kwargs ) :
" We must initialize the parent first - important! "
2013-07-12 15:57:46 +02:00
super ( SharedMemoryModel , self ) . __init__ ( * args , * * kwargs )
#SharedMemoryModel.__init__(self, *args, **kwargs)
2013-06-05 18:47:41 +02:00
_SA ( self , " dbobj " , self ) # this allows for self-reference
_SA ( self , " locks " , LockHandler ( self ) )
2013-08-24 23:57:44 +02:00
_SA ( self , " permissions " , PermissionHandler ( self ) )
2013-10-15 20:00:18 +02:00
_SA ( self , " nattributes " , 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
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 :
2013-06-06 12:45:39 +02:00
if propname . startswith ( ' _ ' ) :
# don't relay private/special varname lookups to the typeclass
raise AttributeError ( " private property %s not found on db model (typeclass not searched). " % propname )
2010-08-29 18:46:58 +00:00
# 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
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
# 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
2013-11-14 19:31:17 +01:00
# where '*' is either OBJECT, SCRIPT or PLAYER depending on the
# typed entities).
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 )
2013-05-11 16:09:26 +02:00
#print "typeclass:",typeclass,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-11-05 00:55:25 +01:00
_SA ( self , " 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 ( )
2013-05-12 22:13:05 +02:00
except AttributeError :
logger . log_trace ( " \n %s : Error initializing typeclass %s . Using default. " % ( self , tpath ) )
break
2011-10-01 22:00:22 +02:00
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.
2013-11-14 19:31:17 +01:00
# If we reach this point we couldn't import any typeclasses. Return
# default. It's up to the calling method to use e.g. self.is_typeclass()
# to detect that the result is not the one asked for.
2012-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 )
2013-11-14 19:31:17 +01: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 :
2013-11-14 19:31:17 +01:00
# 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
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.
2013-11-14 19:31:17 +01: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 ) :
2013-11-14 19:31:17 +01:00
# if this is still giving an error, Evennia is wrongly
# configured or buggy
2011-08-06 18:15:04 +00:00
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
2013-09-22 22:57:03 +02:00
def is_typeclass ( self , typeclass , exact = True ) :
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
2013-11-14 19:31:17 +01:00
typeclasses = [ typeclass ] + [ " %s . %s " % ( path , typeclass )
for path in _GA ( self , " _typeclass_paths " ) ]
2011-08-06 18:15:04 +00:00
if exact :
2012-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 ( )
2013-11-14 19:31:17 +01:00
if any ( ( " %s . %s " % ( _GA ( cls , " __module__ " ) ,
_GA ( cls , " __name__ " ) ) == typec
for typec in typeclasses ) ) ) )
2013-05-11 20:01:19 +02:00
2013-05-14 08:44:40 +02:00
def delete ( self , * args , * * kwargs ) :
"""
Type - level cleanup
"""
super ( TypedObject , self ) . delete ( * args , * * kwargs )
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
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
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 :
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
#
2013-08-24 23:57:44 +02:00
# Lock / permission methods
#
def access ( self , accessing_obj , access_type = ' read ' , default = False ) :
"""
Determines if another object has permission to access .
accessing_obj - object trying to access this one
access_type - type of access sought
default - what to return if no lock of access_type was found
"""
return self . locks . check ( accessing_obj , access_type = access_type , default = default )
def check_permstring ( self , permstring ) :
"""
2013-11-14 19:31:17 +01:00
This explicitly checks if we hold particular permission without
involving any locks .
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
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 ) :
_SA ( self , ' obj ' , obj )
_SA ( self , " attrhandler " , _GA ( _GA ( self , " 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 :
class NdbHolder ( object ) :
" Holder for storing non-attr_obj attributes. "
def get_all ( self ) :
return [ val for val in self . __dict__ . keys ( )
if not val . startswith ( ' _ ' ) ]
all = property ( get_all )
2013-11-14 19:31:17 +01:00
2013-08-24 23:57:44 +02:00
def __getattribute__ ( self , key ) :
# return None if no matching attribute was found.
try :
return _GA ( self , key )
except AttributeError :
return None
2013-11-14 19:31:17 +01:00
2013-08-24 23:57:44 +02:00
def __setattr__ ( self , key , value ) :
# hook the oob handler here
#call_ndb_hooks(self, key, value)
_SA ( self , key , value )
self . _ndb_holder = NdbHolder ( )
return self . _ndb_holder
2013-11-14 19:31:17 +01:00
2013-08-24 23:57:44 +02:00
#@ndb.setter
def __ndb_set ( self , value ) :
" Stop accidentally replacing the db object "
string = " Cannot assign directly to ndb object! "
string = " Use ndb.attr=value instead. "
raise Exception ( string )
2013-11-14 19:31:17 +01:00
2013-08-24 23:57:44 +02:00
#@ndb.deleter
def __ndb_del ( self ) :
" Stop accidental deletion. "
raise Exception ( " Cannot delete the ndb object! " )
ndb = property ( __ndb_get , __ndb_set , __ndb_del )
#
2013-10-15 20:00:18 +02:00
# ***** DEPRECATED METHODS BELOW *******
2010-08-29 18:46:58 +00:00
#
2012-03-30 23:47:22 +02:00
#
2013-10-21 21:17:32 +02:00
# Full attr_obj attributes. You usually access these
2012-03-30 23:47:22 +02:00
# through the obj.db.attrname method.
2013-07-08 18:13:21 +02:00
# Helper methods for attr_obj 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
"""
2013-08-24 23:57:44 +02:00
logger . log_depmsg ( " obj.has_attribute() is deprecated. Use obj.attributes.has(). " )
2013-08-24 21:23:43 +02:00
return _GA ( self , " attributes " ) . has ( attribute_name )
2013-02-03 17:00:46 +01:00
def set_attribute ( self , attribute_name , new_value = None , lockstring = " " ) :
2010-08-29 18:46:58 +00:00
"""
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 .
2013-02-03 17:00:46 +01:00
lockstring - this sets an access restriction on the attribute object . Note that
this is normally NOT checked - use the secureattr ( ) access method
below to perform access - checked modification of attributes . Lock
types checked by secureattr are ' attrread ' , ' attredit ' , ' attrcreate ' .
2010-08-29 18:46:58 +00:00
"""
2013-08-24 23:57:44 +02:00
logger . log_depmsg ( " obj.set_attribute() is deprecated. Use obj.db.attr=value or obj.attributes.add(). " )
2013-08-24 21:23:43 +02:00
_GA ( self , " attributes " ) . add ( attribute_name , new_value , lockstring = lockstring )
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
"""
2013-08-24 23:57:44 +02:00
logger . log_depmsg ( " obj.get_attribute_obj() is deprecated. Use obj.attributes.get(..., return_obj=True) " )
2013-08-24 21:23:43 +02:00
return _GA ( self , " attributes " ) . get ( attribute_name , default = default , return_obj = True )
2012-06-10 21:46:00 +02:00
2013-07-08 18:13:21 +02:00
def get_attribute ( self , attribute_name , default = None , raise_exception = False ) :
2010-08-29 18:46:58 +00:00
"""
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
2013-07-21 09:44:34 -05:00
raise_exception ( bool ) - raise an exception if no object exists instead of returning default .
2010-08-29 18:46:58 +00:00
"""
2013-08-24 23:57:44 +02:00
logger . log_depmsg ( " obj.get_attribute() is deprecated. Use obj.db.attr or obj.attributes.get(). " )
2013-08-24 21:23:43 +02:00
return _GA ( self , " attributes " ) . get ( attribute_name , default = default , raise_exception = raise_exception )
2013-07-08 18:13:21 +02:00
def del_attribute ( self , attribute_name , raise_exception = False ) :
2010-08-29 18:46:58 +00:00
"""
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.
2013-07-08 18:13:21 +02:00
raise_exception ( bool ) - raise exception if attribute to delete
could not be found
2010-08-29 18:46:58 +00:00
"""
2013-08-24 23:57:44 +02:00
logger . log_depmsg ( " obj.del_attribute() is deprecated. Use del obj.db.attr or obj.attributes.remove(). " )
2013-08-24 21:23:43 +02:00
_GA ( self , " attributes " ) . remove ( attribute_name , raise_exception = raise_exception )
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
"""
2013-08-24 23:57:44 +02:00
logger . log_depmsg ( " obj.get_all_attributes() is deprecated. Use obj.db.all() or obj.attributes.all(). " )
2013-08-24 21:23:43 +02:00
return _GA ( self , " attributes " ) . all ( )
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
"""
2013-08-24 23:57:44 +02:00
logger . log_depmsg ( " obj.attr() is deprecated. Use handlers obj.db or obj.attributes. " )
2013-11-14 19:31:17 +01:00
if attribute_name is None :
2010-08-29 18:46:58 +00:00
# act as a list method
2013-08-25 17:09:03 +02:00
return _GA ( self , " attributes " ) . all ( )
2013-11-14 19:31:17 +01:00
elif delete is True :
2013-08-25 17:09:03 +02:00
_GA ( self , " attributes " ) . remove ( attribute_name )
2013-11-14 19:31:17 +01:00
elif value is None :
2010-08-29 18:46:58 +00:00
# act as a getter.
2013-08-25 17:09:03 +02:00
return _GA ( self , " attributes " ) . get ( attribute_name )
2010-08-29 18:46:58 +00:00
else :
# act as a setter
2013-08-25 17:09:03 +02:00
self . _GA ( self , " attributes " ) . add ( attribute_name , value )
2010-08-29 18:46:58 +00:00
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 .
"""
2013-08-24 23:57:44 +02:00
logger . log_depmsg ( " obj.secure_attr() is deprecated. Use obj.attributes methods, giving accessing_obj keyword. " )
2013-11-14 19:31:17 +01:00
if attribute_name is None :
2013-08-24 21:23:43 +02:00
return _GA ( self , " attributes " ) . all ( accessing_obj = accessing_object , default_access = default_access_read )
2013-11-14 19:31:17 +01:00
elif delete is True :
2012-06-10 21:46:00 +02:00
# act as deleter
2013-08-24 21:23:43 +02:00
_GA ( self , " attributes " ) . remove ( attribute_name , accessing_obj = accessing_object , default_access = default_access_edit )
2013-11-14 19:31:17 +01:00
elif value is None :
2012-06-10 21:46:00 +02:00
# act as getter
2013-08-24 21:23:43 +02:00
return _GA ( self , " attributes " ) . get ( attribute_name , accessing_obj = accessing_object , default_access = default_access_read )
2012-06-10 21:46:00 +02:00
else :
# act as setter
2013-08-25 17:09:03 +02:00
attr = _GA ( self , " attributes " ) . get ( attribute_name , return_obj = True )
2012-06-10 21:46:00 +02:00
if attr :
# attribute already exists
2013-08-24 21:23:43 +02:00
_GA ( self , " attributes " ) . add ( attribute_name , value , accessing_obj = accessing_object , default_access = default_access_edit )
2012-06-10 21:46:00 +02:00
else :
# creating a new attribute - check access on storing object!
2013-08-24 21:23:43 +02:00
_GA ( self , " attributes " ) . add ( attribute_name , value , accessing_obj = accessing_object , default_access = default_access_create )
2012-06-10 21:46:00 +02:00
2013-10-15 20:00:18 +02:00
def nattr ( self , attribute_name = None , value = None , delete = False ) :
2010-08-29 18:46:58 +00:00
"""
2013-10-15 20:00:18 +02:00
This allows for assigning non - persistent data on the object using
a method call . Will return None if trying to access a non - existing property .
2010-08-29 18:46:58 +00:00
"""
2013-10-15 20:00:18 +02:00
logger . log_depmsg ( " obj.nattr() is deprecated. Use obj.nattributes instead. " )
2013-11-14 19:31:17 +01:00
if attribute_name is None :
2013-10-15 20:00:18 +02:00
# act as a list method
if callable ( self . ndb . all ) :
return self . ndb . all ( )
else :
return [ val for val in self . ndb . __dict__ . keys ( )
if not val . startswith [ ' _ ' ] ]
2013-11-14 19:31:17 +01:00
elif delete is True :
2013-10-15 20:00:18 +02:00
if hasattr ( self . ndb , attribute_name ) :
_DA ( _GA ( self , " ndb " ) , attribute_name )
2013-11-14 19:31:17 +01:00
elif value is None :
2013-10-15 20:00:18 +02:00
# act as a getter.
if hasattr ( self . ndb , attribute_name ) :
_GA ( _GA ( self , " ndb " ) , attribute_name )
else :
return None
else :
# act as a setter
_SA ( self . ndb , attribute_name , value )
2010-08-29 18:46:58 +00:00
2013-08-24 21:23:43 +02:00
2013-07-08 18:13:21 +02:00