从 ABC 和 django.db.models.Model 继承会引发元类异常

Posted

技术标签:

【中文标题】从 ABC 和 django.db.models.Model 继承会引发元类异常【英文标题】:Inheriting from both ABC and django.db.models.Model raises metaclass exception 【发布时间】:2018-10-09 16:07:44 【问题描述】:

我正在尝试使用Python 3实现一个Django数据模型类,它也是一个接口类。我这样做的原因是,我正在为我的同事编写一个基类,需要他实现三个方法在他从我那里获得的所有课程中。我试图给他一种简化的方式来使用我设计的系统的功能。但是,他必须重写一些方法来为系统提供足够的信息来执行他继承的类中的代码。

我知道这是错误的,因为它会引发异常,但我希望有一个类似于以下示例的类:

from django.db import models
from abc import ABC, abstractmethod

class AlgorithmTemplate(ABC, models.Model):
    name = models.CharField(max_length=32)

    @abstractmethod
    def data_subscriptions(self):
        """
        This method returns a list of topics this class will subscribe to using websockets

        NOTE: This method MUST be overriden!
        
        :rtype: list
        """

我知道我可以避免从 ABC 类继承,但我想使用它,因为我不会在这里让你感到厌烦。

问题


在我的项目中包含一个类(如上面的类)并运行 python manage.py makemigrations 后,我收到错误:TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases。我搜索了 Stack Overflow,但只找到了以下解决方案:

class M_A(type): pass
class M_B(type): pass
class A(metaclass=M_A): pass
class B(metaclass=M_B): pass

class M_C(M_A, M_B): pass
class C:(A, B, metaclass=M_C): pass

我已阅读以下帖子:

Using ABC, PolymorphicModel, django-models gives metaclass conflict

Resolving metaclass conflicts

我已经尝试了这些解决方案的许多变体,但我仍然遇到可怕的metaclass 异常。帮助我欧比旺克诺比,你是我唯一的希望。 :-)

【问题讨论】:

我重新写了我的问题,因为在收到我的问题格式不正确的反馈后,我重新阅读了它。甚至我也很难理解自己的问题!一定是在我因编码过多而眼花缭乱时写的。大声笑 【参考方案1】:

我也有同样的需求,找到了this。为了清晰和完整,我已经更改了代码。基本上,您需要一个额外的类,您可以将其用于所有模型接口。

import abc

from django.db import models


class AbstractModelMeta(abc.ABCMeta, type(models.Model)):
    pass


class AbstractModel(models.Model, metaclass=AbstractModelMeta):    
    # You may have common fields here.

    class Meta:
        abstract = True

    @abc.abstractmethod
    def must_implement(self):
        pass


class MyModel(AbstractModel):
    code = models.CharField("code", max_length=10, unique=True)

    class Meta:
        app_label = 'my_app'


test = MyModel(code='test')
> TypeError: Can't instantiate abstract class MyModel with abstract methods must_implement

现在你拥有两全其美。

【讨论】:

【参考方案2】:

我找到了一个适合我的解决方案,所以我想我会在这里发布它以防它帮助其他人。我决定不从ABC 类继承,而是在“抽象”方法(派生类必须实现的方法)中引发异常。我确实在 Django 文档中找到了有用的信息,描述了使用 Django 数据模型作为抽象基类以及多表继承。

作为抽象基类的 Django 数据模型


引用自the docs:

当您想将一些通用信息放入许多其他模型中时,抽象基类很有用。您编写基类并将 abstract=True 放在 Meta 类中。该模型将不会用于创建任何数据库表。相反,当它用作其他模型的基类时,它的字段将添加到子类的字段中。

一个例子:

from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

Student 模型将包含三个字段:name、age 和 home_group。 CommonInfo 模型不能用作普通的 Django 模型,因为它 是一个抽象基类。它不生成数据库表或 有管理器,不能直接实例化或保存。

从抽象基类继承的字段可以用 另一个字段或值,或者用 None 删除。

使用 Django 数据模型进行多表继承


我对“多表继承”的理解是,你可以定义一个数据模型,然后也可以将它用作第二个数据模型的基类。第二个数据模型将继承第一个模型的所有字段,以及它自己的字段。

引用自the docs:

Django 支持的第二种模型继承是当每个 层次结构中的模型本身就是一个模型。每个型号 对应自己的数据库表,可以查询和创建 分别。继承关系引入了 子模型及其每个父模型(通过自动创建的 一对一字段)。例如:

from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

Place 的所有字段也将在 Restaurant 中可用, 尽管数据将驻留在不同的数据库表中。所以这些 都是可能的:

>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")

【讨论】:

问题是关于结合 ABC 和 Django 模型。 ABC 确保您不能初始化 AbstractBase 只要不是所有的抽象方法都已定义。这与 Django 的抽象模型略有不同。

以上是关于从 ABC 和 django.db.models.Model 继承会引发元类异常的主要内容,如果未能解决你的问题,请参考以下文章

Django - 指定 django.db.models.(???) 字段形式的宽度

如何使用Django.db.models Q模块查询多行用户输入文本数据

AttributeError:模块'django.db.models'没有属性'DataField'[关闭]

为啥 django.forms.CharField 中缺少“空白”,但在 django.db.models.CharField 中存在?

getattr - 异常值:模块 'django.db.models' 没有属性 'model_name''

Django:我如何在 django.db.models.query QuerySet 中重载 Q 以在我的管理器中用于特殊目的