我有 django 模型类,它继承了两个类 - TranslatableModel和MPPTModel。它会导致元类冲突——Python 不知道要使用哪个基类的元类。
所以,我需要编写自己的元类来协调两个元类 - TranslatableModelBase和MPTTModelBase。
我应该如何编写自己的元类?
class TranslatableModelBase(ModelBase):
"""
Metaclass for models with translated fields (TranslatableModel)
"""
def __new__(cls, name, bases, attrs):
super_new = super(TranslatableModelBase, cls).__new__
parents = [b for b in bases if isinstance(b, TranslatableModelBase)]
if not parents:
# If this isn't a subclass of TranslatableModel, don't do anything special.
return super_new(cls, name, bases, attrs)
new_model = super_new(cls, name, bases, attrs)
if not isinstance(new_model.objects, TranslationManager):
raise ImproperlyConfigured(
"The default manager on a TranslatableModel must be a "
"TranslationManager instance or an instance of a subclass of "
"TranslationManager, the default manager of %r is not." %
new_model)
opts = new_model._meta
if opts.abstract:
return new_model
found = False
for relation in new_model.__dict__.keys():
try:
obj = getattr(new_model, relation)
except AttributeError:
continue
if not hasattr(obj, 'related'):
continue
if not hasattr(obj.related, 'model'):
continue
if getattr(obj.related.model._meta, 'shared_model', None) is new_model:
if found:
raise ImproperlyConfigured(
"A TranslatableModel can only define one set of "
"TranslatedFields, %r defines more than one: %r to %r "
"and %r to %r and possibly more" % (new_model, obj,
obj.related.model, found, found.related.model))
else:
new_model.contribute_translations(obj.related)
found = obj
if not found:
raise ImproperlyConfigured(
"No TranslatedFields found on %r, subclasses of "
"TranslatableModel must define TranslatedFields." % new_model
)
post_save.connect(new_model.save_translations, sender=new_model, weak=False)
if not isinstance(opts.get_field_by_name, SmartGetFieldByName):
smart_get_field_by_name = SmartGetFieldByName(opts.get_field_by_name)
opts.get_field_by_name = MethodType(smart_get_field_by_name , opts,
opts.__class__)
return new_model
class MPTTModelBase(ModelBase):
"""
Metaclass for MPTT models
"""
def __new__(meta, class_name, bases, class_dict):
"""
Create subclasses of MPTTModel. This:
- adds the MPTT fields to the class
- adds a TreeManager to the model
"""
MPTTMeta = class_dict.pop('MPTTMeta', None)
if not MPTTMeta:
class MPTTMeta:
pass
initial_options = set(dir(MPTTMeta))
# extend MPTTMeta from base classes
for base in bases:
if hasattr(base, '_mptt_meta'):
for (name, value) in base._mptt_meta:
if name == 'tree_manager_attr':
continue
if name not in initial_options:
setattr(MPTTMeta, name, value)
class_dict['_mptt_meta'] = MPTTOptions(MPTTMeta)
cls = super(MPTTModelBase, meta).__new__(meta, class_name, bases, class_dict)
return meta.register(cls)
@classmethod
def register(meta, cls, **kwargs):
"""
For the weird cases when you need to add tree-ness to an *existing*
class. For other cases you should subclass MPTTModel instead of calling this.
"""
if not issubclass(cls, models.Model):
raise ValueError(_("register() expects a Django model class argument"))
if not hasattr(cls, '_mptt_meta'):
cls._mptt_meta = MPTTOptions(**kwargs)
abstract = getattr(cls._meta, 'abstract', False)
# For backwards compatibility with existing libraries, we copy the
# _mptt_meta options into _meta.
# This was removed in 0.5 but added back in 0.5.1 since it caused compatibility
# issues with django-cms 2.2.0.
# some discussion is here: https://github.com/divio/django-cms/issues/1079
# This stuff is still documented as removed, and WILL be removed again in the next release.
# All new code should use _mptt_meta rather than _meta for tree attributes.
attrs = set(['left_attr', 'right_attr', 'tree_id_attr', 'level_attr', 'parent_attr',
'tree_manager_attr', 'order_insertion_by'])
warned_attrs = set()
class _MetaSubClass(cls._meta.__class__):
def __getattr__(self, attr):
if attr in attrs:
if attr not in warned_attrs:
warnings.warn(
"%s._meta.%s is deprecated and will be removed in mptt 0.6"
% (cls.__name__, attr),
#don't use DeprecationWarning, that gets ignored by default
UserWarning,
)
warned_attrs.add(attr)
return getattr(cls._mptt_meta, attr)
return super(_MetaSubClass, self).__getattr__(attr)
cls._meta.__class__ = _MetaSubClass
try:
MPTTModel
except NameError:
# We're defining the base class right now, so don't do anything
# We only want to add this stuff to the subclasses.
# (Otherwise if field names are customized, we'll end up adding two
# copies)
pass
else:
if not issubclass(cls, MPTTModel):
bases = list(cls.__bases__)
# strip out bases that are strict superclasses of MPTTModel.
# (i.e. Model, object)
# this helps linearize the type hierarchy if possible
for i in range(len(bases) - 1, -1, -1):
if issubclass(MPTTModel, bases[i]):
del bases[i]
bases.insert(0, MPTTModel)
cls.__bases__ = tuple(bases)
for key in ('left_attr', 'right_attr', 'tree_id_attr', 'level_attr'):
field_name = getattr(cls._mptt_meta, key)
try:
cls._meta.get_field(field_name)
except models.FieldDoesNotExist:
field = models.PositiveIntegerField(db_index=True, editable=False)
field.contribute_to_class(cls, field_name)
# Add a tree manager, if there isn't one already
if not abstract:
manager = getattr(cls, 'objects', None)
if manager is None:
manager = cls._default_manager._copy_to_model(cls)
manager.contribute_to_class(cls, 'objects')
elif manager.model != cls:
# manager was inherited
manager = manager._copy_to_model(cls)
manager.contribute_to_class(cls, 'objects')
if hasattr(manager, 'init_from_model'):
manager.init_from_model(cls)
# make sure we have a tree manager somewhere
tree_manager = None
for attr in sorted(dir(cls)):
try:
obj = getattr(cls, attr)
except AttributeError:
continue
if isinstance(obj, TreeManager):
tree_manager = obj
# prefer any locally defined manager (i.e. keep going if not local)
if obj.model is cls:
break
if tree_manager and tree_manager.model is not cls:
tree_manager = tree_manager._copy_to_model(cls)
elif tree_manager is None:
tree_manager = TreeManager()
tree_manager.contribute_to_class(cls, '_tree_manager')
tree_manager.init_from_model(cls)
# avoid using ManagerDescriptor, so instances can refer to self._tree_manager
setattr(cls, '_tree_manager', tree_manager)
# for backwards compatibility, add .tree too (or whatever's in tree_manager_attr)
tree_manager_attr = cls._mptt_meta.tree_manager_attr
if tree_manager_attr != 'objects':
another = getattr(cls, tree_manager_attr, None)
if another is None:
# wrap with a warning on first use
from django.db.models.manager import ManagerDescriptor
class _WarningDescriptor(ManagerDescriptor):
def __init__(self, manager):
self.manager = manager
self.used = False
def __get__(self, instance, type=None):
if instance != None:
raise AttributeError("Manager isn't accessible via %s instances" % type.__name__)
if not self.used:
warnings.warn(
'Implicit manager %s.%s will be removed in django-mptt 0.6. '
' Explicitly define a TreeManager() on your model to remove this warning.'
% (cls.__name__, tree_manager_attr),
DeprecationWarning
)
self.used = True
return self.manager
setattr(cls, tree_manager_attr, _WarningDescriptor(tree_manager))
elif hasattr(another, 'init_from_model'):
another.init_from_model(cls)
return cls