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.
|
||||
"""
|
||||
from src.comms import Msg, TempMsg
|
||||
from src.typeclasses.typeclass import TypeClass
|
||||
from src.comms.models import Msg, TempMsg, ChannelDB
|
||||
from src.typeclasses.models import TypeclassBase
|
||||
from src.utils import logger
|
||||
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
|
||||
types of communication channels.
|
||||
"""
|
||||
__metaclass__ = TypeclassBase
|
||||
|
||||
# helper methods, for easy overloading
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,6 @@ class ObjectDB(TypedObject):
|
|||
contents - other objects having this object as location
|
||||
exits - exits from this object
|
||||
"""
|
||||
|
||||
#
|
||||
# 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 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.utils.logger import log_depmsg
|
||||
|
||||
|
|
@ -31,11 +32,12 @@ _DA = object.__delattr__
|
|||
# Base class to inherit from.
|
||||
#
|
||||
|
||||
class Object(TypeClass):
|
||||
class Object(ObjectDB):
|
||||
"""
|
||||
This is the base class for all in-game objects. Inherit from this
|
||||
to create different types of objects in the game.
|
||||
"""
|
||||
__metaclass__ = TypeclassBase
|
||||
# __init__ is only defined here in order to present docstring to API.
|
||||
def __init__(self, dbobj):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ instead for most things).
|
|||
|
||||
import datetime
|
||||
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.utils import logger
|
||||
__all__ = ("Player",)
|
||||
|
|
@ -23,10 +24,12 @@ _CMDSET_PLAYER = settings.CMDSET_PLAYER
|
|||
_CONNECT_CHANNEL = None
|
||||
|
||||
|
||||
class Player(TypeClass):
|
||||
class Player(PlayerDB):
|
||||
"""
|
||||
Base typeclass for all Players.
|
||||
"""
|
||||
__metaclass__ = TypeclassBase
|
||||
|
||||
def __init__(self, dbobj):
|
||||
"""
|
||||
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 django.conf import settings
|
||||
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.comms import channelhandler
|
||||
from src.utils import logger
|
||||
|
|
@ -108,11 +108,12 @@ class ExtendedLoopingCall(LoopingCall):
|
|||
#
|
||||
# Base script, inherit from Script below instead.
|
||||
#
|
||||
class ScriptBase(TypeClass):
|
||||
class ScriptBase(ScriptDB):
|
||||
"""
|
||||
Base class for scripts. Don't inherit from this, inherit
|
||||
from the class 'Script' instead.
|
||||
"""
|
||||
__metaclass__ = TypeclassBase
|
||||
# private methods
|
||||
|
||||
def __eq__(self, other):
|
||||
|
|
|
|||
|
|
@ -13,45 +13,91 @@ _GA = object.__getattribute__
|
|||
_Tag = None
|
||||
|
||||
#
|
||||
# helper functions for the TypedObjectManager.
|
||||
# Decorators
|
||||
#
|
||||
|
||||
def returns_typeclass_list(method):
|
||||
"""
|
||||
Decorator: Changes return of the decorated method (which are
|
||||
TypeClassed objects) into object_classes(s) instead. Will always
|
||||
return a list (may be empty).
|
||||
Decorator: Always returns a list, even
|
||||
if it is empty.
|
||||
"""
|
||||
def func(self, *args, **kwargs):
|
||||
"decorator. Returns a list."
|
||||
self.__doc__ = method.__doc__
|
||||
matches = make_iter(method(self, *args, **kwargs))
|
||||
return [(hasattr(dbobj, "typeclass") and dbobj.typeclass) or dbobj
|
||||
for dbobj in make_iter(matches)]
|
||||
return list(method(self, *args, **kwargs))
|
||||
return update_wrapper(func, 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):
|
||||
"decorator. Returns result or None."
|
||||
self.__doc__ = method.__doc__
|
||||
matches = method(self, *args, **kwargs)
|
||||
dbobj = matches and make_iter(matches)[0] or None
|
||||
if dbobj:
|
||||
return (hasattr(dbobj, "typeclass") and dbobj.typeclass) or dbobj
|
||||
return None
|
||||
query = method(self, *args, **kwargs)
|
||||
return list(query)[0] if query else None
|
||||
return update_wrapper(func, method)
|
||||
|
||||
# Managers
|
||||
|
||||
|
||||
class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
||||
"""
|
||||
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
|
||||
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 traceback
|
||||
import weakref
|
||||
from importlib import import_module
|
||||
|
||||
from django.db import models
|
||||
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):
|
||||
"""
|
||||
|
|
@ -795,11 +835,23 @@ class TypedObject(SharedMemoryModel):
|
|||
# quick on-object typeclass cache for speed
|
||||
_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):
|
||||
"We must initialize the parent first - important!"
|
||||
typeclass_path = kwargs.pop("typeclass", None)
|
||||
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
|
||||
@lazy_property
|
||||
|
|
@ -872,37 +924,6 @@ class TypedObject(SharedMemoryModel):
|
|||
def __unicode__(self):
|
||||
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
|
||||
def __dbid_get(self):
|
||||
"""
|
||||
|
|
@ -936,208 +957,6 @@ class TypedObject(SharedMemoryModel):
|
|||
raise Exception("dbref cannot be deleted!")
|
||||
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
|
||||
#
|
||||
|
|
@ -1428,168 +1247,3 @@ class TypedObject(SharedMemoryModel):
|
|||
raise Exception("Cannot delete the ndb object!")
|
||||
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
|
||||
|
||||
# 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__
|
||||
_SA = object.__setattr__
|
||||
_DA = object.__delattr__
|
||||
|
|
@ -72,7 +87,7 @@ class SharedMemoryModelBase(ModelBase):
|
|||
cls._idmapper_recache_protection = False
|
||||
super(SharedMemoryModelBase, cls)._prepare()
|
||||
|
||||
def __new__(cls, classname, bases, classdict, *args, **kwargs):
|
||||
def __new__(cls, name, bases, attrs):
|
||||
"""
|
||||
Field shortcut creation:
|
||||
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)
|
||||
fdel = lambda cls: _del(cls, fieldname) if editable else _del_nonedit(cls,fieldname)
|
||||
# assigning
|
||||
classdict[wrappername] = property(fget, fset, fdel)
|
||||
attrs[wrappername] = property(fget, fset, fdel)
|
||||
#type(cls).__setattr__(cls, wrappername, property(fget, fset, fdel))#, doc))
|
||||
|
||||
# exclude some models that should not auto-create wrapper fields
|
||||
if cls.__name__ in ("ServerConfig", "TypeNick"):
|
||||
return
|
||||
# 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"):
|
||||
foreignkey = type(field).__name__ == "ForeignKey"
|
||||
#print fieldname, type(field).__name__, field
|
||||
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
|
||||
#print "wrapping %s -> %s" % (fieldname, wrappername)
|
||||
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):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue