1

我有 django 模型类,它继承了两个类 - TranslatableModelMPPTModel。它会导致元类冲突——Python 不知道要使用哪个基类的元类。

所以,我需要编写自己的元类来协调两个元类 - TranslatableModelBaseMPTTModelBase

我应该如何编写自己的元类?

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
4

0 回答 0