First non-tested version of moving typeclasses to proxy models.
This commit is contained in:
parent
8e8d85a4fe
commit
236c0d17d3
9 changed files with 403 additions and 622 deletions
|
|
@ -3,17 +3,18 @@ Default Typeclass for Comms.
|
||||||
|
|
||||||
See objects.objects for more information on Typeclassing.
|
See objects.objects for more information on Typeclassing.
|
||||||
"""
|
"""
|
||||||
from src.comms import Msg, TempMsg
|
from src.comms.models import Msg, TempMsg, ChannelDB
|
||||||
from src.typeclasses.typeclass import TypeClass
|
from src.typeclasses.models import TypeclassBase
|
||||||
from src.utils import logger
|
from src.utils import logger
|
||||||
from src.utils.utils import make_iter
|
from src.utils.utils import make_iter
|
||||||
|
|
||||||
|
|
||||||
class Channel(TypeClass):
|
class Channel(ChannelDB):
|
||||||
"""
|
"""
|
||||||
This is the base class for all Comms. Inherit from this to create different
|
This is the base class for all Comms. Inherit from this to create different
|
||||||
types of communication channels.
|
types of communication channels.
|
||||||
"""
|
"""
|
||||||
|
__metaclass__ = TypeclassBase
|
||||||
|
|
||||||
# helper methods, for easy overloading
|
# helper methods, for easy overloading
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,6 @@ class ObjectDB(TypedObject):
|
||||||
contents - other objects having this object as location
|
contents - other objects having this object as location
|
||||||
exits - exits from this object
|
exits - exits from this object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#
|
#
|
||||||
# ObjectDB Database model setup
|
# ObjectDB Database model setup
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ they control by simply linking to a new object's user property.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from src.typeclasses.typeclass import TypeClass
|
from src.objects.models import ObjectDB
|
||||||
|
from src.typeclasses.models import TypeclassBase
|
||||||
from src.commands import cmdset, command
|
from src.commands import cmdset, command
|
||||||
from src.utils.logger import log_depmsg
|
from src.utils.logger import log_depmsg
|
||||||
|
|
||||||
|
|
@ -31,11 +32,12 @@ _DA = object.__delattr__
|
||||||
# Base class to inherit from.
|
# Base class to inherit from.
|
||||||
#
|
#
|
||||||
|
|
||||||
class Object(TypeClass):
|
class Object(ObjectDB):
|
||||||
"""
|
"""
|
||||||
This is the base class for all in-game objects. Inherit from this
|
This is the base class for all in-game objects. Inherit from this
|
||||||
to create different types of objects in the game.
|
to create different types of objects in the game.
|
||||||
"""
|
"""
|
||||||
|
__metaclass__ = TypeclassBase
|
||||||
# __init__ is only defined here in order to present docstring to API.
|
# __init__ is only defined here in order to present docstring to API.
|
||||||
def __init__(self, dbobj):
|
def __init__(self, dbobj):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ instead for most things).
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from src.typeclasses.typeclass import TypeClass
|
from src.players.models import PlayerDB
|
||||||
|
from src.typeclasses.models import TypeclassBase
|
||||||
from src.comms.models import ChannelDB
|
from src.comms.models import ChannelDB
|
||||||
from src.utils import logger
|
from src.utils import logger
|
||||||
__all__ = ("Player",)
|
__all__ = ("Player",)
|
||||||
|
|
@ -23,10 +24,12 @@ _CMDSET_PLAYER = settings.CMDSET_PLAYER
|
||||||
_CONNECT_CHANNEL = None
|
_CONNECT_CHANNEL = None
|
||||||
|
|
||||||
|
|
||||||
class Player(TypeClass):
|
class Player(PlayerDB):
|
||||||
"""
|
"""
|
||||||
Base typeclass for all Players.
|
Base typeclass for all Players.
|
||||||
"""
|
"""
|
||||||
|
__metaclass__ = TypeclassBase
|
||||||
|
|
||||||
def __init__(self, dbobj):
|
def __init__(self, dbobj):
|
||||||
"""
|
"""
|
||||||
This is the base Typeclass for all Players. Players represent
|
This is the base Typeclass for all Players. Players represent
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from twisted.internet.defer import Deferred, maybeDeferred
|
||||||
from twisted.internet.task import LoopingCall
|
from twisted.internet.task import LoopingCall
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from src.typeclasses.typeclass import TypeClass
|
from src.typeclasses.models import TypeclassBase
|
||||||
from src.scripts.models import ScriptDB
|
from src.scripts.models import ScriptDB
|
||||||
from src.comms import channelhandler
|
from src.comms import channelhandler
|
||||||
from src.utils import logger
|
from src.utils import logger
|
||||||
|
|
@ -108,11 +108,12 @@ class ExtendedLoopingCall(LoopingCall):
|
||||||
#
|
#
|
||||||
# Base script, inherit from Script below instead.
|
# Base script, inherit from Script below instead.
|
||||||
#
|
#
|
||||||
class ScriptBase(TypeClass):
|
class ScriptBase(ScriptDB):
|
||||||
"""
|
"""
|
||||||
Base class for scripts. Don't inherit from this, inherit
|
Base class for scripts. Don't inherit from this, inherit
|
||||||
from the class 'Script' instead.
|
from the class 'Script' instead.
|
||||||
"""
|
"""
|
||||||
|
__metaclass__ = TypeclassBase
|
||||||
# private methods
|
# private methods
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
|
|
||||||
|
|
@ -13,45 +13,91 @@ _GA = object.__getattribute__
|
||||||
_Tag = None
|
_Tag = None
|
||||||
|
|
||||||
#
|
#
|
||||||
# helper functions for the TypedObjectManager.
|
# Decorators
|
||||||
#
|
#
|
||||||
|
|
||||||
def returns_typeclass_list(method):
|
def returns_typeclass_list(method):
|
||||||
"""
|
"""
|
||||||
Decorator: Changes return of the decorated method (which are
|
Decorator: Always returns a list, even
|
||||||
TypeClassed objects) into object_classes(s) instead. Will always
|
if it is empty.
|
||||||
return a list (may be empty).
|
|
||||||
"""
|
"""
|
||||||
def func(self, *args, **kwargs):
|
def func(self, *args, **kwargs):
|
||||||
"decorator. Returns a list."
|
|
||||||
self.__doc__ = method.__doc__
|
self.__doc__ = method.__doc__
|
||||||
matches = make_iter(method(self, *args, **kwargs))
|
return list(method(self, *args, **kwargs))
|
||||||
return [(hasattr(dbobj, "typeclass") and dbobj.typeclass) or dbobj
|
|
||||||
for dbobj in make_iter(matches)]
|
|
||||||
return update_wrapper(func, method)
|
return update_wrapper(func, method)
|
||||||
|
|
||||||
|
|
||||||
def returns_typeclass(method):
|
def returns_typeclass(method):
|
||||||
"""
|
"""
|
||||||
Decorator: Will always return a single typeclassed result or None.
|
Decorator: Returns a single match or None
|
||||||
"""
|
"""
|
||||||
def func(self, *args, **kwargs):
|
def func(self, *args, **kwargs):
|
||||||
"decorator. Returns result or None."
|
|
||||||
self.__doc__ = method.__doc__
|
self.__doc__ = method.__doc__
|
||||||
matches = method(self, *args, **kwargs)
|
query = method(self, *args, **kwargs)
|
||||||
dbobj = matches and make_iter(matches)[0] or None
|
return list(query)[0] if query else None
|
||||||
if dbobj:
|
|
||||||
return (hasattr(dbobj, "typeclass") and dbobj.typeclass) or dbobj
|
|
||||||
return None
|
|
||||||
return update_wrapper(func, method)
|
return update_wrapper(func, method)
|
||||||
|
|
||||||
# Managers
|
# Managers
|
||||||
|
|
||||||
|
|
||||||
class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
||||||
"""
|
"""
|
||||||
Common ObjectManager for all dbobjects.
|
Common ObjectManager for all dbobjects.
|
||||||
"""
|
"""
|
||||||
|
# common methods for all typed managers. These are used
|
||||||
|
# in other methods. Returns querysets.
|
||||||
|
|
||||||
|
def get(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Overload the standard get. This will limit itself to only
|
||||||
|
return the current typeclass.
|
||||||
|
"""
|
||||||
|
kwargs.update({"db_typeclass_path":self.model.path})
|
||||||
|
return super(TypedObjectManager, self).get(**kwargs)
|
||||||
|
|
||||||
|
def filter(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Overload of the standard filter function. This filter will
|
||||||
|
limit itself to only the current typeclass.
|
||||||
|
"""
|
||||||
|
kwargs.update({"db_typeclass_path":self.model.path})
|
||||||
|
return super(TypedObjectManager, self).filter(**kwargs)
|
||||||
|
|
||||||
|
def all(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Overload method to return all matches, filtering for typeclass
|
||||||
|
"""
|
||||||
|
return super(TypedObjectManager, self).all(**kwargs).filter(db_typeclass_path=self.model.path)
|
||||||
|
|
||||||
|
def get_inherit(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Variation of get that not only returns the current
|
||||||
|
typeclass but also all subclasses of that typeclass.
|
||||||
|
"""
|
||||||
|
paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__)
|
||||||
|
for cls in self.model.__subclasses__()]
|
||||||
|
kwargs.update({"db_typeclass_path__in":paths})
|
||||||
|
return super(TypedObjectManager, self).get(**kwargs)
|
||||||
|
|
||||||
|
def filter_inherit(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Variation of filter that allows results both from typeclass
|
||||||
|
and from subclasses of typeclass
|
||||||
|
"""
|
||||||
|
# query, including all subclasses
|
||||||
|
paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__)
|
||||||
|
for cls in self.model.__subclasses__()]
|
||||||
|
kwargs.update({"db_typeclass_path__in":paths})
|
||||||
|
return super(TypedObjectManager, self).filter(**kwargs)
|
||||||
|
|
||||||
|
def all_inherit(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Return all matches, allowing matches from all subclasses of
|
||||||
|
the typeclass.
|
||||||
|
"""
|
||||||
|
paths = [self.model.path] + ["%s.%s" % (cls.__module__, cls.__name__)
|
||||||
|
for cls in self.model.__subclasses__()]
|
||||||
|
return super(TypedObjectManager, self).all(**kwargs).filter(db_typeclass_path__in=paths)
|
||||||
|
|
||||||
|
|
||||||
# Attribute manager methods
|
# Attribute manager methods
|
||||||
def get_attribute(self, key=None, category=None, value=None, strvalue=None, obj=None, attrtype=None):
|
def get_attribute(self, key=None, category=None, value=None, strvalue=None, obj=None, attrtype=None):
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import sys
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
import weakref
|
import weakref
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
@ -738,6 +739,45 @@ class PermissionHandler(TagHandler):
|
||||||
#
|
#
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
|
# imported for access by other
|
||||||
|
from src.utils.idmapper.base import SharedMemoryModelBase
|
||||||
|
|
||||||
|
class TypeclassModelBase(SharedMemoryModelBase):
|
||||||
|
"""
|
||||||
|
Metaclass for typeclasses
|
||||||
|
"""
|
||||||
|
def __init__(cls, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
We must define our Typeclasses as proxies. We also store the path
|
||||||
|
directly on the class, this is useful for managers.
|
||||||
|
"""
|
||||||
|
super(TypeclassModelBase, cls).__init__(*args, **kwargs)
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
cls.Meta = Meta
|
||||||
|
cls.typename = cls.__name__
|
||||||
|
cls.path = "%s.%s" % (cls.__module__, cls.__name__)
|
||||||
|
|
||||||
|
|
||||||
|
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 __init__(cls, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
We must define our Typeclasses as proxies. We also store the path
|
||||||
|
directly on the class, this is useful for managers.
|
||||||
|
"""
|
||||||
|
super(TypeclassBase, cls).__init__(*args, **kwargs)
|
||||||
|
class Meta:
|
||||||
|
# this is the important bit
|
||||||
|
proxy = True
|
||||||
|
cls.Meta = Meta
|
||||||
|
# convenience for manager methods
|
||||||
|
cls.typename = cls.__name__
|
||||||
|
cls.path = "%s.%s" % (cls.__module__, cls.__name__)
|
||||||
|
|
||||||
|
|
||||||
class TypedObject(SharedMemoryModel):
|
class TypedObject(SharedMemoryModel):
|
||||||
"""
|
"""
|
||||||
|
|
@ -795,11 +835,23 @@ class TypedObject(SharedMemoryModel):
|
||||||
# quick on-object typeclass cache for speed
|
# quick on-object typeclass cache for speed
|
||||||
_cached_typeclass = None
|
_cached_typeclass = None
|
||||||
|
|
||||||
# lock handler self.locks
|
# typeclass mechanism
|
||||||
|
|
||||||
|
def _import_class(self, path):
|
||||||
|
path, clsname = path.rsplit(".", 1)
|
||||||
|
mod = import_module(path)
|
||||||
|
return getattr(mod, clsname)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"We must initialize the parent first - important!"
|
typeclass_path = kwargs.pop("typeclass", None)
|
||||||
super(TypedObject, self).__init__(*args, **kwargs)
|
super(TypedObject, self).__init__(*args, **kwargs)
|
||||||
_SA(self, "dbobj", self) # this allows for self-reference
|
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__)
|
||||||
|
|
||||||
# initialize all handlers in a lazy fashion
|
# initialize all handlers in a lazy fashion
|
||||||
@lazy_property
|
@lazy_property
|
||||||
|
|
@ -872,37 +924,6 @@ class TypedObject(SharedMemoryModel):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u"%s" % _GA(self, "db_key")
|
return u"%s" % _GA(self, "db_key")
|
||||||
|
|
||||||
def __getattribute__(self, propname):
|
|
||||||
"""
|
|
||||||
Will predominantly look for an attribute
|
|
||||||
on this object, but if not found we will
|
|
||||||
check if it might exist on the typeclass instead. Since
|
|
||||||
the typeclass refers back to the databaseobject as well, we
|
|
||||||
have to be very careful to avoid loops.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return _GA(self, propname)
|
|
||||||
except AttributeError:
|
|
||||||
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)
|
|
||||||
# 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.)
|
|
||||||
return _GA(_GA(self, 'typeclass'), propname)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
#@property
|
#@property
|
||||||
def __dbid_get(self):
|
def __dbid_get(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -936,208 +957,6 @@ class TypedObject(SharedMemoryModel):
|
||||||
raise Exception("dbref cannot be deleted!")
|
raise Exception("dbref cannot be deleted!")
|
||||||
dbref = property(__dbref_get, __dbref_set, __dbref_del)
|
dbref = property(__dbref_get, __dbref_set, __dbref_del)
|
||||||
|
|
||||||
# the latest 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 = ""
|
|
||||||
|
|
||||||
# typeclass property
|
|
||||||
#@property
|
|
||||||
def __typeclass_get(self):
|
|
||||||
"""
|
|
||||||
Getter. Allows for value = self.typeclass.
|
|
||||||
The typeclass is a class object found at self.typeclass_path;
|
|
||||||
it allows for extending the Typed object for all different
|
|
||||||
types of objects that the game needs. This property
|
|
||||||
handles loading and initialization of the typeclass on the fly.
|
|
||||||
|
|
||||||
Note: The liberal use of _GA and __setattr__ (instead
|
|
||||||
of normal dot notation) is due to optimization: it avoids calling
|
|
||||||
the custom self.__getattribute__ more than necessary.
|
|
||||||
"""
|
|
||||||
path = _GA(self, "typeclass_path")
|
|
||||||
typeclass = _GA(self, "_cached_typeclass")
|
|
||||||
try:
|
|
||||||
if typeclass and _GA(typeclass, "path") == path:
|
|
||||||
# don't call at_init() when returning from cache
|
|
||||||
return typeclass
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
errstring = ""
|
|
||||||
if not path:
|
|
||||||
# this means we should get the default obj without giving errors.
|
|
||||||
return _GA(self, "_get_default_typeclass")(cache=True, silent=True, save=True)
|
|
||||||
else:
|
|
||||||
# handle loading/importing of typeclasses, searching all paths.
|
|
||||||
# (self._typeclass_paths is a shortcut to settings.TYPECLASS_*_PATHS
|
|
||||||
# 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')]
|
|
||||||
|
|
||||||
for tpath in typeclass_paths:
|
|
||||||
|
|
||||||
# try to import and analyze the result
|
|
||||||
typeclass = _GA(self, "_path_import")(tpath)
|
|
||||||
if callable(typeclass):
|
|
||||||
# we succeeded to import. Cache and return.
|
|
||||||
_SA(self, "typeclass_path", tpath)
|
|
||||||
typeclass = typeclass(self)
|
|
||||||
_SA(self, "_cached_typeclass", typeclass)
|
|
||||||
try:
|
|
||||||
typeclass.at_init()
|
|
||||||
except AttributeError:
|
|
||||||
logger.log_trace("\n%s: Error initializing typeclass %s. Using default." % (self, tpath))
|
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
logger.log_trace()
|
|
||||||
return typeclass
|
|
||||||
elif hasattr(typeclass, '__file__'):
|
|
||||||
errstring += "\n%s seems to be just the path to a module. You need" % tpath
|
|
||||||
errstring += " to specify the actual typeclass name inside the module too."
|
|
||||||
elif typeclass:
|
|
||||||
errstring += "\n%s" % typeclass.strip() # this will hold a growing error message.
|
|
||||||
if not errstring:
|
|
||||||
errstring = "\nMake sure the path is set correctly. Paths tested:\n"
|
|
||||||
errstring += ", ".join(typeclass_paths)
|
|
||||||
errstring += "\nTypeclass code was not found or failed to load."
|
|
||||||
# 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.
|
|
||||||
_GA(self, "_display_errmsg")(errstring.strip())
|
|
||||||
return _GA(self, "_get_default_typeclass")(cache=False, silent=False, save=False)
|
|
||||||
|
|
||||||
#@typeclass.deleter
|
|
||||||
def __typeclass_del(self):
|
|
||||||
"Deleter. Disallow 'del self.typeclass'"
|
|
||||||
raise Exception("The typeclass property should never be deleted, only changed in-place!")
|
|
||||||
|
|
||||||
# typeclass property
|
|
||||||
typeclass = property(__typeclass_get, fdel=__typeclass_del)
|
|
||||||
|
|
||||||
|
|
||||||
def _path_import(self, path):
|
|
||||||
"""
|
|
||||||
Import a class from a python path of the
|
|
||||||
form src.objects.object.Object
|
|
||||||
"""
|
|
||||||
errstring = ""
|
|
||||||
if not path:
|
|
||||||
# this needs not be bad, it just means
|
|
||||||
# we should use defaults.
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
modpath, class_name = path.rsplit('.', 1)
|
|
||||||
module = __import__(modpath, fromlist=["none"])
|
|
||||||
return module.__dict__[class_name]
|
|
||||||
except ImportError:
|
|
||||||
trc = sys.exc_traceback
|
|
||||||
if not trc.tb_next:
|
|
||||||
# we separate between not finding the module, and finding
|
|
||||||
# a buggy one.
|
|
||||||
pass
|
|
||||||
#errstring = "Typeclass not found trying path '%s'." % path
|
|
||||||
else:
|
|
||||||
# a bug in the module is reported normally.
|
|
||||||
trc = traceback.format_exc().strip()
|
|
||||||
errstring = "\n%sError importing '%s'." % (trc, path)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
errstring = "Malformed typeclass path '%s'." % path
|
|
||||||
except KeyError:
|
|
||||||
errstring = "No class '%s' was found in module '%s'."
|
|
||||||
errstring = errstring % (class_name, modpath)
|
|
||||||
except Exception:
|
|
||||||
trc = traceback.format_exc().strip()
|
|
||||||
errstring = "\n%sException importing '%s'." % (trc, path)
|
|
||||||
# return the error.
|
|
||||||
return errstring
|
|
||||||
|
|
||||||
def _display_errmsg(self, message):
|
|
||||||
"""
|
|
||||||
Helper function to display error.
|
|
||||||
"""
|
|
||||||
_SA(self, "typeclass_last_errmsg", message)
|
|
||||||
if ServerConfig.objects.conf("server_starting_mode"):
|
|
||||||
print message
|
|
||||||
else:
|
|
||||||
logger.log_errmsg(message)
|
|
||||||
return
|
|
||||||
|
|
||||||
def _get_default_typeclass(self, cache=False, silent=False, save=False):
|
|
||||||
"""
|
|
||||||
This is called when a typeclass fails to
|
|
||||||
load for whatever reason.
|
|
||||||
Overload this in different entities.
|
|
||||||
|
|
||||||
Default operation is to load a default typeclass.
|
|
||||||
"""
|
|
||||||
defpath = _GA(self, "_default_typeclass_path")
|
|
||||||
typeclass = _GA(self, "_path_import")(defpath)
|
|
||||||
# if not silent:
|
|
||||||
# #errstring = "\n\nUsing Default class '%s'." % defpath
|
|
||||||
# _GA(self, "_display_errmsg")(errstring)
|
|
||||||
|
|
||||||
if not callable(typeclass):
|
|
||||||
# if typeclass still doesn't exist at this point, we're in trouble.
|
|
||||||
# fall back to hardcoded core class which is wrong for e.g.
|
|
||||||
# scripts/players etc.
|
|
||||||
failpath = defpath
|
|
||||||
defpath = "src.objects.objects.Object"
|
|
||||||
typeclass = _GA(self, "_path_import")(defpath)
|
|
||||||
if not silent:
|
|
||||||
#errstring = " %s\n%s" % (typeclass, errstring)
|
|
||||||
errstring = " Default class '%s' failed to load." % failpath
|
|
||||||
errstring += "\n Using Evennia's default root '%s'." % defpath
|
|
||||||
_GA(self, "_display_errmsg")(errstring.strip())
|
|
||||||
if not callable(typeclass):
|
|
||||||
# if this is still giving an error, Evennia is wrongly
|
|
||||||
# configured or buggy
|
|
||||||
raise Exception("CRITICAL ERROR: The final fallback typeclass %s cannot load!!" % defpath)
|
|
||||||
typeclass = typeclass(self)
|
|
||||||
if save:
|
|
||||||
_SA(self, 'db_typeclass_path', defpath)
|
|
||||||
_GA(self, 'save')()
|
|
||||||
if cache:
|
|
||||||
_SA(self, "_cached_db_typeclass_path", defpath)
|
|
||||||
|
|
||||||
_SA(self, "_cached_typeclass", typeclass)
|
|
||||||
try:
|
|
||||||
typeclass.at_init()
|
|
||||||
except Exception:
|
|
||||||
logger.log_trace()
|
|
||||||
return typeclass
|
|
||||||
|
|
||||||
def is_typeclass(self, typeclass, exact=True):
|
|
||||||
"""
|
|
||||||
Returns true if this object has this type
|
|
||||||
OR has a typeclass which is an subclass of
|
|
||||||
the given typeclass. This operates on the actually
|
|
||||||
loaded typeclass (this is important since a failing
|
|
||||||
typeclass may instead have its default currently loaded)
|
|
||||||
|
|
||||||
typeclass - can be a class object or the
|
|
||||||
python path to such an object to match against.
|
|
||||||
|
|
||||||
exact - returns true only if the object's
|
|
||||||
type is exactly this typeclass, ignoring
|
|
||||||
parents.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
typeclass = _GA(typeclass, "path")
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
typeclasses = [typeclass] + ["%s.%s" % (path, typeclass)
|
|
||||||
for path in _GA(self, "_typeclass_paths")]
|
|
||||||
if exact:
|
|
||||||
current_path = _GA(self.typeclass, "path") #"_GA(self, "_cached_db_typeclass_path")
|
|
||||||
return typeclass and any((current_path == typec for typec in typeclasses))
|
|
||||||
else:
|
|
||||||
# check parent chain
|
|
||||||
return any((cls for cls in self.typeclass.__class__.mro()
|
|
||||||
if any(("%s.%s" % (_GA(cls, "__module__"),
|
|
||||||
_GA(cls, "__name__")) == typec
|
|
||||||
for typec in typeclasses))))
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Object manipulation methods
|
# Object manipulation methods
|
||||||
#
|
#
|
||||||
|
|
@ -1428,168 +1247,3 @@ class TypedObject(SharedMemoryModel):
|
||||||
raise Exception("Cannot delete the ndb object!")
|
raise Exception("Cannot delete the ndb object!")
|
||||||
ndb = property(__ndb_get, __ndb_set, __ndb_del)
|
ndb = property(__ndb_get, __ndb_set, __ndb_del)
|
||||||
|
|
||||||
# #
|
|
||||||
# # ***** DEPRECATED METHODS BELOW *******
|
|
||||||
# #
|
|
||||||
#
|
|
||||||
# #
|
|
||||||
# # Full attr_obj attributes. You usually access these
|
|
||||||
# # through the obj.db.attrname method.
|
|
||||||
#
|
|
||||||
# # Helper methods for attr_obj attributes
|
|
||||||
#
|
|
||||||
# def has_attribute(self, attribute_name):
|
|
||||||
# """
|
|
||||||
# See if we have an attribute set on the object.
|
|
||||||
#
|
|
||||||
# attribute_name: (str) The attribute's name.
|
|
||||||
# """
|
|
||||||
# logger.log_depmsg("obj.has_attribute() is deprecated. Use obj.attributes.has().")
|
|
||||||
# return _GA(self, "attributes").has(attribute_name)
|
|
||||||
#
|
|
||||||
# def set_attribute(self, attribute_name, new_value=None, lockstring=""):
|
|
||||||
# """
|
|
||||||
# Sets an attribute on an object. Creates the attribute if need
|
|
||||||
# be.
|
|
||||||
#
|
|
||||||
# attribute_name: (str) The attribute's name.
|
|
||||||
# new_value: (python obj) The value to set the attribute to. If this is not
|
|
||||||
# a str, the object will be stored as a pickle.
|
|
||||||
# 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'.
|
|
||||||
# """
|
|
||||||
# logger.log_depmsg("obj.set_attribute() is deprecated. Use obj.db.attr=value or obj.attributes.add().")
|
|
||||||
# _GA(self, "attributes").add(attribute_name, new_value, lockstring=lockstring)
|
|
||||||
#
|
|
||||||
# def get_attribute_obj(self, attribute_name, default=None):
|
|
||||||
# """
|
|
||||||
# Get the actual attribute object named attribute_name
|
|
||||||
# """
|
|
||||||
# logger.log_depmsg("obj.get_attribute_obj() is deprecated. Use obj.attributes.get(..., return_obj=True)")
|
|
||||||
# return _GA(self, "attributes").get(attribute_name, default=default, return_obj=True)
|
|
||||||
#
|
|
||||||
# def get_attribute(self, attribute_name, default=None, raise_exception=False):
|
|
||||||
# """
|
|
||||||
# Returns the value of an attribute on an object. You may need to
|
|
||||||
# type cast the returned value from this function since the attribute
|
|
||||||
# can be of any type. Returns default if no match is found.
|
|
||||||
#
|
|
||||||
# attribute_name: (str) The attribute's name.
|
|
||||||
# default: What to return if no attribute is found
|
|
||||||
# raise_exception (bool) - raise an exception if no object exists instead of returning default.
|
|
||||||
# """
|
|
||||||
# logger.log_depmsg("obj.get_attribute() is deprecated. Use obj.db.attr or obj.attributes.get().")
|
|
||||||
# return _GA(self, "attributes").get(attribute_name, default=default, raise_exception=raise_exception)
|
|
||||||
#
|
|
||||||
# def del_attribute(self, attribute_name, raise_exception=False):
|
|
||||||
# """
|
|
||||||
# Removes an attribute entirely.
|
|
||||||
#
|
|
||||||
# attribute_name: (str) The attribute's name.
|
|
||||||
# raise_exception (bool) - raise exception if attribute to delete
|
|
||||||
# could not be found
|
|
||||||
# """
|
|
||||||
# logger.log_depmsg("obj.del_attribute() is deprecated. Use del obj.db.attr or obj.attributes.remove().")
|
|
||||||
# _GA(self, "attributes").remove(attribute_name, raise_exception=raise_exception)
|
|
||||||
#
|
|
||||||
# def get_all_attributes(self):
|
|
||||||
# """
|
|
||||||
# Returns all attributes defined on the object.
|
|
||||||
# """
|
|
||||||
# logger.log_depmsg("obj.get_all_attributes() is deprecated. Use obj.db.all() or obj.attributes.all().")
|
|
||||||
# return _GA(self, "attributes").all()
|
|
||||||
#
|
|
||||||
# def attr(self, attribute_name=None, value=None, delete=False):
|
|
||||||
# """
|
|
||||||
# This is a convenient wrapper for
|
|
||||||
# get_attribute, set_attribute, del_attribute
|
|
||||||
# and get_all_attributes.
|
|
||||||
# If value is None, attr will act like
|
|
||||||
# a getter, otherwise as a setter.
|
|
||||||
# set delete=True to delete the named attribute.
|
|
||||||
#
|
|
||||||
# Note that you cannot set the attribute
|
|
||||||
# value to None using this method. Use set_attribute.
|
|
||||||
# """
|
|
||||||
# logger.log_depmsg("obj.attr() is deprecated. Use handlers obj.db or obj.attributes.")
|
|
||||||
# if attribute_name is None:
|
|
||||||
# # act as a list method
|
|
||||||
# return _GA(self, "attributes").all()
|
|
||||||
# elif delete is True:
|
|
||||||
# _GA(self, "attributes").remove(attribute_name)
|
|
||||||
# elif value is None:
|
|
||||||
# # act as a getter.
|
|
||||||
# return _GA(self, "attributes").get(attribute_name)
|
|
||||||
# else:
|
|
||||||
# # act as a setter
|
|
||||||
# self._GA(self, "attributes").add(attribute_name, value)
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# """
|
|
||||||
# logger.log_depmsg("obj.secure_attr() is deprecated. Use obj.attributes methods, giving accessing_obj keyword.")
|
|
||||||
# if attribute_name is None:
|
|
||||||
# return _GA(self, "attributes").all(accessing_obj=accessing_object, default_access=default_access_read)
|
|
||||||
# elif delete is True:
|
|
||||||
# # act as deleter
|
|
||||||
# _GA(self, "attributes").remove(attribute_name, accessing_obj=accessing_object, default_access=default_access_edit)
|
|
||||||
# elif value is None:
|
|
||||||
# # act as getter
|
|
||||||
# return _GA(self, "attributes").get(attribute_name, accessing_obj=accessing_object, default_access=default_access_read)
|
|
||||||
# else:
|
|
||||||
# # act as setter
|
|
||||||
# attr = _GA(self, "attributes").get(attribute_name, return_obj=True)
|
|
||||||
# if attr:
|
|
||||||
# # attribute already exists
|
|
||||||
# _GA(self, "attributes").add(attribute_name, value, accessing_obj=accessing_object, default_access=default_access_edit)
|
|
||||||
# else:
|
|
||||||
# # creating a new attribute - check access on storing object!
|
|
||||||
# _GA(self, "attributes").add(attribute_name, value, accessing_obj=accessing_object, default_access=default_access_create)
|
|
||||||
#
|
|
||||||
# def nattr(self, attribute_name=None, value=None, delete=False):
|
|
||||||
# """
|
|
||||||
# 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.
|
|
||||||
# """
|
|
||||||
# logger.log_depmsg("obj.nattr() is deprecated. Use obj.nattributes instead.")
|
|
||||||
# if attribute_name is None:
|
|
||||||
# # act as a list method
|
|
||||||
# if callable(self.ndb.all):
|
|
||||||
# return self.ndb.all()
|
|
||||||
# else:
|
|
||||||
# return [val for val in self.ndb.__dict__.keys()
|
|
||||||
# if not val.startswith['_']]
|
|
||||||
# elif delete is True:
|
|
||||||
# if hasattr(self.ndb, attribute_name):
|
|
||||||
# _DA(_GA(self, "ndb"), attribute_name)
|
|
||||||
# elif value is None:
|
|
||||||
# # 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)
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,190 +0,0 @@
|
||||||
"""
|
|
||||||
A typeclass is the companion of a TypedObject django model.
|
|
||||||
It 'decorates' the model without actually having to add new
|
|
||||||
fields to the model - transparently storing data onto its
|
|
||||||
associated model without the admin/user just having to deal
|
|
||||||
with a 'normal' Python class. The only restrictions is that
|
|
||||||
the typeclass must inherit from TypeClass and not reimplement
|
|
||||||
the get/setters defined below. There are also a few properties
|
|
||||||
that are protected, so as to not overwrite property names
|
|
||||||
used by the typesystem or django itself.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from src.utils.logger import log_trace, log_errmsg
|
|
||||||
|
|
||||||
__all__ = ("TypeClass",)
|
|
||||||
|
|
||||||
# these are called so many times it's worth to avoid lookup calls
|
|
||||||
_GA = object.__getattribute__
|
|
||||||
_SA = object.__setattr__
|
|
||||||
_DA = object.__delattr__
|
|
||||||
|
|
||||||
# To ensure the sanity of the model, there are a
|
|
||||||
# few property names we won't allow the admin to
|
|
||||||
# set on the typeclass just like that. Note that these are *not* related
|
|
||||||
# to *in-game* safety (if you can edit typeclasses you have
|
|
||||||
# full access anyway), so no protection against changing
|
|
||||||
# e.g. 'locks' or 'permissions' should go here.
|
|
||||||
PROTECTED = ('id', 'dbobj', 'db', 'ndb', 'objects', 'typeclass', 'db_player',
|
|
||||||
'attr', 'save', 'delete', 'db_model_name','attribute_class',
|
|
||||||
'typeclass_paths')
|
|
||||||
|
|
||||||
|
|
||||||
# If this is true, all non-protected property assignments
|
|
||||||
# are directly stored to a database attribute
|
|
||||||
|
|
||||||
class MetaTypeClass(type):
|
|
||||||
"""
|
|
||||||
This metaclass just makes sure the class object gets
|
|
||||||
printed in a nicer way (it might end up having no name at all
|
|
||||||
otherwise due to the magics being done with get/setattribute).
|
|
||||||
"""
|
|
||||||
def __init__(mcs, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Adds some features to typeclassed objects
|
|
||||||
"""
|
|
||||||
super(MetaTypeClass, mcs).__init__(*args, **kwargs)
|
|
||||||
mcs.typename = mcs.__name__
|
|
||||||
mcs.path = "%s.%s" % (mcs.__module__, mcs.__name__)
|
|
||||||
|
|
||||||
def __str__(cls):
|
|
||||||
return "%s" % cls.__name__
|
|
||||||
|
|
||||||
|
|
||||||
class TypeClass(object):
|
|
||||||
"""
|
|
||||||
This class implements a 'typeclass' object. This is connected
|
|
||||||
to a database object inheriting from TypedObject.
|
|
||||||
the TypeClass allows for all customization.
|
|
||||||
Most of the time this means that the admin never has to
|
|
||||||
worry about database access but only deal with extending
|
|
||||||
TypeClasses to create diverse objects in the game.
|
|
||||||
|
|
||||||
The ObjectType class has all functionality for wrapping a
|
|
||||||
database object transparently.
|
|
||||||
|
|
||||||
It's up to its child classes to implement eventual custom hooks
|
|
||||||
and other functions called by the engine.
|
|
||||||
|
|
||||||
"""
|
|
||||||
__metaclass__ = MetaTypeClass
|
|
||||||
|
|
||||||
def __init__(self, dbobj):
|
|
||||||
"""
|
|
||||||
Initialize the object class. There are two ways to call this class.
|
|
||||||
o = object_class(dbobj) : this is used to initialize dbobj with the
|
|
||||||
class name
|
|
||||||
o = dbobj.object_class(dbobj) : this is used when dbobj.object_class
|
|
||||||
is already set.
|
|
||||||
|
|
||||||
"""
|
|
||||||
# typecheck of dbobj - we can't allow it to be added here
|
|
||||||
# unless it's really a TypedObject.
|
|
||||||
dbobj_cls = _GA(dbobj, '__class__')
|
|
||||||
dbobj_mro = _GA(dbobj_cls, '__mro__')
|
|
||||||
if not any('src.typeclasses.models.TypedObject' in str(mro) for mro in dbobj_mro):
|
|
||||||
raise Exception("dbobj is not a TypedObject: %s: %s" % (dbobj_cls, dbobj_mro))
|
|
||||||
|
|
||||||
# we should always be able to use dbobj/typeclass to get back an object of the desired type
|
|
||||||
_SA(self, 'dbobj', dbobj)
|
|
||||||
_SA(self, 'typeclass', self)
|
|
||||||
|
|
||||||
def __getattribute__(self, propname):
|
|
||||||
"""
|
|
||||||
Change the normal property access to
|
|
||||||
transparently include the properties on
|
|
||||||
self.dbobj. Note that dbobj properties have
|
|
||||||
priority, so if you define a same-named
|
|
||||||
property on the class, it will NOT be
|
|
||||||
accessible through getattr.
|
|
||||||
"""
|
|
||||||
if propname.startswith('__') and propname.endswith('__'):
|
|
||||||
# python specials are parsed as-is (otherwise things like
|
|
||||||
# isinstance() fail to identify the typeclass)
|
|
||||||
return _GA(self, propname)
|
|
||||||
#print "get %s (dbobj:%s)" % (propname, type(dbobj))
|
|
||||||
try:
|
|
||||||
return _GA(self, propname)
|
|
||||||
except AttributeError:
|
|
||||||
try:
|
|
||||||
dbobj = _GA(self, 'dbobj')
|
|
||||||
except AttributeError:
|
|
||||||
log_trace("Typeclass CRITICAL ERROR! dbobj not found for Typeclass %s!" % self)
|
|
||||||
raise
|
|
||||||
try:
|
|
||||||
return _GA(dbobj, propname)
|
|
||||||
except AttributeError:
|
|
||||||
string = "Object: '%s' not found on %s(#%s), nor on its typeclass %s."
|
|
||||||
raise AttributeError(string % (propname, dbobj, _GA(dbobj, "dbid"), _GA(dbobj, "typeclass_path")))
|
|
||||||
|
|
||||||
def __setattr__(self, propname, value):
|
|
||||||
"""
|
|
||||||
Transparently save data. Use property on Typeclass only if
|
|
||||||
that property is already defined, otherwise relegate to the
|
|
||||||
dbobj object in all situations. Note that this does not
|
|
||||||
necessarily mean storing it to the database.
|
|
||||||
"""
|
|
||||||
#print "set %s -> %s" % (propname, value)
|
|
||||||
if propname in PROTECTED:
|
|
||||||
string = "%s: '%s' is a protected attribute name."
|
|
||||||
string += " (protected: [%s])" % (", ".join(PROTECTED))
|
|
||||||
log_errmsg(string % (self.name, propname))
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
_GA(self, propname)
|
|
||||||
_SA(self, propname, value)
|
|
||||||
except AttributeError:
|
|
||||||
try:
|
|
||||||
dbobj = _GA(self, 'dbobj')
|
|
||||||
except AttributeError:
|
|
||||||
dbobj = None
|
|
||||||
if dbobj:
|
|
||||||
_SA(dbobj, propname, value)
|
|
||||||
else:
|
|
||||||
# only as a last resort do we save on the typeclass object
|
|
||||||
_SA(self, propname, value)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
"""
|
|
||||||
dbobj-recognized comparison
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return _GA(_GA(self, "dbobj"), "dbid") == _GA(_GA(other, "dbobj"), "dbid")
|
|
||||||
except AttributeError:
|
|
||||||
return id(self) == id(other)
|
|
||||||
|
|
||||||
def __delattr__(self, propname):
|
|
||||||
"""
|
|
||||||
Transparently deletes data from the typeclass or dbobj by first
|
|
||||||
searching on the typeclass, secondly on the dbobj.db.
|
|
||||||
Will not allow deletion of properties stored directly on dbobj.
|
|
||||||
"""
|
|
||||||
if propname in PROTECTED:
|
|
||||||
string = "%s: '%s' is a protected attribute name."
|
|
||||||
string += " (protected: [%s])" % (", ".join(PROTECTED))
|
|
||||||
log_errmsg(string % (self.name, propname))
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
_DA(self, propname)
|
|
||||||
except AttributeError:
|
|
||||||
# not on typeclass, try to delete on db/ndb
|
|
||||||
try:
|
|
||||||
dbobj = _GA(self, 'dbobj')
|
|
||||||
except AttributeError:
|
|
||||||
log_trace("This is probably due to an unsafe reload.")
|
|
||||||
return # ignore delete
|
|
||||||
try:
|
|
||||||
dbobj.del_attribute(propname, raise_exception=True)
|
|
||||||
except AttributeError:
|
|
||||||
string = "Object: '%s' not found on %s(#%s), nor on its typeclass %s."
|
|
||||||
raise AttributeError(string % (propname, dbobj,
|
|
||||||
dbobj.dbid,
|
|
||||||
dbobj.typeclass_path,))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"represent the object"
|
|
||||||
return self.key
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return u"%s" % self.key
|
|
||||||
|
|
@ -22,6 +22,21 @@ from manager import SharedMemoryManager
|
||||||
|
|
||||||
AUTO_FLUSH_MIN_INTERVAL = 60.0 * 5 # at least 5 mins between cache flushes
|
AUTO_FLUSH_MIN_INTERVAL = 60.0 * 5 # at least 5 mins between cache flushes
|
||||||
|
|
||||||
|
# django patch imports
|
||||||
|
import copy
|
||||||
|
import sys
|
||||||
|
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
|
||||||
|
from django.core.exceptions import (ObjectDoesNotExist,
|
||||||
|
MultipleObjectsReturned, FieldError)
|
||||||
|
from django.apps.config import MODELS_MODULE_NAME
|
||||||
|
from django.db.models.fields.related import OneToOneField
|
||||||
|
#/ django patch imports
|
||||||
|
|
||||||
|
|
||||||
_GA = object.__getattribute__
|
_GA = object.__getattribute__
|
||||||
_SA = object.__setattr__
|
_SA = object.__setattr__
|
||||||
_DA = object.__delattr__
|
_DA = object.__delattr__
|
||||||
|
|
@ -72,7 +87,7 @@ class SharedMemoryModelBase(ModelBase):
|
||||||
cls._idmapper_recache_protection = False
|
cls._idmapper_recache_protection = False
|
||||||
super(SharedMemoryModelBase, cls)._prepare()
|
super(SharedMemoryModelBase, cls)._prepare()
|
||||||
|
|
||||||
def __new__(cls, classname, bases, classdict, *args, **kwargs):
|
def __new__(cls, name, bases, attrs):
|
||||||
"""
|
"""
|
||||||
Field shortcut creation:
|
Field shortcut creation:
|
||||||
Takes field names db_* and creates property wrappers named without the db_ prefix. So db_key -> key
|
Takes field names db_* and creates property wrappers named without the db_ prefix. So db_key -> key
|
||||||
|
|
@ -158,23 +173,273 @@ class SharedMemoryModelBase(ModelBase):
|
||||||
fset = lambda cls, val: _set(cls, fieldname, val)
|
fset = lambda cls, val: _set(cls, fieldname, val)
|
||||||
fdel = lambda cls: _del(cls, fieldname) if editable else _del_nonedit(cls,fieldname)
|
fdel = lambda cls: _del(cls, fieldname) if editable else _del_nonedit(cls,fieldname)
|
||||||
# assigning
|
# assigning
|
||||||
classdict[wrappername] = property(fget, fset, fdel)
|
attrs[wrappername] = property(fget, fset, fdel)
|
||||||
#type(cls).__setattr__(cls, wrappername, property(fget, fset, fdel))#, doc))
|
#type(cls).__setattr__(cls, wrappername, property(fget, fset, fdel))#, doc))
|
||||||
|
|
||||||
# exclude some models that should not auto-create wrapper fields
|
# exclude some models that should not auto-create wrapper fields
|
||||||
if cls.__name__ in ("ServerConfig", "TypeNick"):
|
if cls.__name__ in ("ServerConfig", "TypeNick"):
|
||||||
return
|
return
|
||||||
# dynamically create the wrapper properties for all fields not already handled (manytomanyfields are always handlers)
|
# dynamically create the wrapper properties for all fields not already handled (manytomanyfields are always handlers)
|
||||||
for fieldname, field in ((fname, field) for fname, field in classdict.items()
|
for fieldname, field in ((fname, field) for fname, field in attrs.items()
|
||||||
if fname.startswith("db_") and type(field).__name__ != "ManyToManyField"):
|
if fname.startswith("db_") and type(field).__name__ != "ManyToManyField"):
|
||||||
foreignkey = type(field).__name__ == "ForeignKey"
|
foreignkey = type(field).__name__ == "ForeignKey"
|
||||||
#print fieldname, type(field).__name__, field
|
#print fieldname, type(field).__name__, field
|
||||||
wrappername = "dbid" if fieldname == "id" else fieldname.replace("db_", "", 1)
|
wrappername = "dbid" if fieldname == "id" else fieldname.replace("db_", "", 1)
|
||||||
if wrappername not in classdict:
|
if wrappername not in attrs:
|
||||||
# makes sure not to overload manually created wrappers on the model
|
# makes sure not to overload manually created wrappers on the model
|
||||||
#print "wrapping %s -> %s" % (fieldname, wrappername)
|
#print "wrapping %s -> %s" % (fieldname, wrappername)
|
||||||
create_wrapper(cls, fieldname, wrappername, editable=field.editable, foreignkey=foreignkey)
|
create_wrapper(cls, fieldname, wrappername, editable=field.editable, foreignkey=foreignkey)
|
||||||
return super(SharedMemoryModelBase, cls).__new__(cls, classname, bases, classdict, *args, **kwargs)
|
|
||||||
|
|
||||||
|
# django patch
|
||||||
|
# Evennia mod, based on Django Ticket #11560: https://code.djangoproject.com/ticket/11560
|
||||||
|
# The actual patch is small and further down.
|
||||||
|
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
|
||||||
|
# Evennia mod, based on Django Ticket #11560: https://code.djangoproject.com/ticket/11560
|
||||||
|
# This allows multiple inheritance for proxy models
|
||||||
|
while parent._meta.proxy:
|
||||||
|
parent = parent._meta.proxy_for_model
|
||||||
|
if base is not None and base is not parent:
|
||||||
|
#if base is not None:
|
||||||
|
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
|
||||||
|
|
||||||
|
class TypeclassModelBase(SharedMemoryModelBase):
|
||||||
|
"""
|
||||||
|
Metaclass for typeclasses
|
||||||
|
"""
|
||||||
|
def __init__(cls, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
We must define our Typeclasses as proxies. We also store the path
|
||||||
|
directly on the class, this is useful for managers.
|
||||||
|
"""
|
||||||
|
super(TypeclassModelBase, cls).__init__(*args, **kwargs)
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
cls.Meta = Meta
|
||||||
|
cls.typename = cls.__name__
|
||||||
|
cls.path = "%s.%s" % (cls.__module__, cls.__name__)
|
||||||
|
|
||||||
|
|
||||||
class SharedMemoryModel(Model):
|
class SharedMemoryModel(Model):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue