Django:自定义元类继承和扩展`ModelBase`

时间:2017-12-29 16:29:20

标签: python django inheritance metaclass method-resolution-order

我正在尝试做一些元类hocus-pocus。我想要自己的元类 从ModelBase继承,然后我想添加额外的逻辑 扩展其__new__方法。不过我认为有一些东西 我使用MRO /继承顺序的方式很奇怪。

以下是基本情况:

from django.db.models import Model, ModelBase


class CustomMetaclass(ModelBase):
    def __new__(cls, name, bases, attrs):
        # As I am trying to extend `ModelBase`, I was expecting this
        # call to `super` to give me the return value from here:

        # https://github.com/django/django/blob/master/django/db/models/base.py#L300

        # And that I would be able to access everyhing in `_meta` with
        # `clsobj._meta`. But actually this object is
        # `MyAbstractModel` and has no `_meta` property so I'm pretty
        # sure `__new__` isn't being called on `ModelBase` at all at
        # this point.
        clsobj = super().__new__(cls, name, bases, attrs)

        # Now, I want to have access to the `_meta` property setup by
        # `ModelBase` so I can dispatch on the data in there. For
        # example, let's do something with the field definitions.
        for field in clsobj._meta.get_fields():
            do_stuff_with_fields()

        return clsobj


class MyAbstractModel(metaclass=CustomMetaclass):
    """This model is abstract because I only want the custom metaclass
    logic to apply to those models of my choosing and I don't want to
    be able to instantiate it directly. See the class definitions below.
    """
    class Meta:
        abstract = True

class MyModel(Model):
    """Regular model, will be derived from metaclass `ModelBase` as usual.
    """
    pass

class MyCustomisedModel(MyAbstractModel):
    """This model should enjoy the logic defined by our extended `__new__` method.
    """
    pass

__new__ ModelBase CustomMetaClass未被调用的任何想法 ModelBase?如何以这种方式正确扩展FilteredDbSet?我非常确定元类继承是可能的 但似乎我错过了一些东西......

1 个答案:

答案 0 :(得分:0)

获取clsobj _meta属性的方法非常简单:

class CustomMetaclass(ModelBase):
    def __new__(cls, name, bases, attrs):
        bases = (Model,)
        clsobj = super().__new__(cls, name, bases, attrs)

        for field in clsobj._meta.get_fields():
            do_stuff_with_fields()

        return clsobj

我们可以使用MyAbstractModel(Model, metaclass=CustomMetaclass)执行相同的操作。

但是,这里的最终成功仍然取决于我们打算在__new__方法中开展的工作。如果我们想以某种方式反省并使用元编程来处理类的字段,我们需要注意我们正在尝试使用{strong> import 时的__new__重写该类,从而(因为这是Django)app registry尚未就绪,如果出现某些条件(例如我们被禁止访问或使用反向关系),这可能会导致异常被引发。即使Model作为基础传递到__new__,也会发生这种情况。

我们可以通过使用以下对_get_fields的非公开调用来解决其中一些问题(Django在某些地方自行调用):

class CustomMetaclass(ModelBase):
    def __new__(cls, name, bases, attrs):
        bases = (Model,)
        clsobj = super().__new__(cls, name, bases, attrs)

        for field in clsobj._meta._get_fields(reverse=False):
            do_stuff_with_fields()

        return clsobj

但是根据情景和我们想要实现的目标,我们可能仍会遇到问题;例如,我们无法使用我们的元类访问任何反向关系。所以仍然没有好处。

为了克服这一限制,我们必须利用app注册表中的信号,使我们的课程充满活力,因为我们希望他们能够完全访问_meta.get_fields

查看此票证:https://code.djangoproject.com/ticket/24231

主要内容是:" Django模型类不允许您在准备好的应用程序注册表的上下文之外使用。"