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

Django: Custom Metaclass Inheriting From And Extending `ModelBase`

我正在尝试做一些元类魔术。我想要我自己的元类 从 ModelBase 继承,然后我想添加额外的逻辑 扩展其 __new__ 方法。但是我觉得有些东西 MRO/inheritance 命令在我使用它的方式中发生了奇怪的事情。

基本情况如下:

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

知道为什么 ModelBase 上的 __new__ 没有被调用 CustomMetaClass?如何以这种方式正确扩展 ModelBase ?我很确定元类继承是可能的 但好像我错过了什么...

获取带有 _meta 属性的 clsobj 的方法很简单:

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__ 方法中进行的工作类型。如果我们想以某种方式内省并使用元编程处理 class 的字段,我们需要意识到我们正在尝试使用 处的 __new__ 重写 class import 时间因此(因为这是 Django)app registry 还没有准备好,如果出现某些情况(例如,我们被禁止访问或使用反向关系),这可能会引发异常.即使将 Model 作为基础传递到 __new__ 中,也会发生这种情况。

我们可以通过使用以下对 _get_fields 的非 public 调用(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

但根据具体情况和我们想要实现的目标,我们可能仍然会遇到问题;例如,我们将无法使用我们的 metaclass 访问任何反向关系。所以还是不行

为了克服这个限制,我们必须利用应用程序注册表中的信号来使我们的 classes 像我们希望的那样动态,并具有对 _meta.get_fields.

的完全访问权限

看到这张票:https://code.djangoproject.com/ticket/24231

主要收获是:"a Django model class is not something you are permitted to work with outside the context of a prepared app registry."