如何在 Django 中维护所有代理模型的 table?

How to maintain a table of all proxy models in Django?

我有一个模型 A,想制作它的子class。

class A(models.Model):
    type = models.ForeignKey(Type)
    data = models.JSONField()
    
    def compute():
            pass

class B(A):
    def compute():
        df = self.go_get_data()
        self.data = self.process(df)

class C(A):
    def compute():
        df = self.go_get_other_data()
        self.data = self.process_another_way(df)

# ... other subclasses of A

BC不应该有自己的table,所以我决定使用Metaproxy属性。但是,我希望所有已实现的代理都有一个 table。 特别是,我想记录每个 subclass 的名称和描述。 例如,对于 B,名称将是 "B",描述将是 B 的文档字符串。 所以我做了另一个模型:

class Type(models.Model):
    # The name of the class
    name = models.String()
    # The docstring of the class
    desc = models.String()
    # A unique identifier, different from the Django ID,
    # that allows for smoothly changing the name of the class
    identifier = models.Int()

现在,我想要它,所以当我创建一个 A 时,我只能在 A 的不同子 class 之间进行选择。 因此 Type table 应该始终是最新的。 例如,如果我想对 B 的行为进行单元测试,我将需要使用相应的 Type 实例来创建 B 的实例,因此 Type 实例已经需要在数据库中

查看 Django website,我看到了两种实现此目的的方法:固定装置和数据迁移。 对于我的用例,固定装置不够动态,因为属性实际上来自代码。这给我留下了数据迁移。

我试过写一个,大概是这样的:

def update_results(apps, schema_editor):
    A = apps.get_model("app", "A")
    Type = apps.get_model("app", "Type")
    subclasses = get_all_subclasses(A)
    for cls in subclasses:
        id = cls.get_identifier()
        Type.objects.update_or_create(
            identifier=id,
            defaults=dict(name=cls.__name__, desc=cls.__desc__)
        )
    
class Migration(migrations.Migration):

    operations = [
        RunPython(update_results)
    ]
    
    # ... other stuff

问题是,我看不到如何在 class 中存储标识符,以便 Django Model 实例可以恢复它。 到目前为止,这是我尝试过的:

我已经尝试使用 Python 的相当新的 __init_subclass__ 结构。所以我的代码现在看起来像:

class A:

    def __init_subclass__(cls, identifier=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if identifier is None:
            raise ValueError()
        cls.identifier = identifier
        Type.objects.update_or_create(
            identifier=identifier,
            defaults=dict(name=cls.__name__, desc=cls.__doc__)
        )
    
    # ... the rest of A

# The identifier should never change, so that even if the
# name of the class changes, we still know which subclass is referred to
class B(A, identifier=3):

    # ... the rest of B

但是这个 update_or_create 在数据库是新的时候失败了(例如在单元测试期间),因为 Type table 不存在。 当我在开发中遇到这个问题时(我们还处于早期阶段所以删除数据库仍​​然是明智的),我必须去 注释掉 __init_subclass__ 中的 update_or_create。然后我可以迁移并将其放回原处。

当然,这个解决方案也不是很好,因为 __init_subclass__ 比 运行 更必要。理想情况下,这种机制只会在迁移时发生。

好了!我希望问题陈述有意义。

感谢您阅读到这里,期待您的来信;即使你有其他事情要做,我也祝你今天好好休息:)

在 Django-expert 朋友的帮助下,我用 post_migrate 信号解决了这个问题。 我删除了 __init_subclass 中的 update_or_create,并在 project/app/apps.py 中添加了:

from django.apps import AppConfig
from django.db.models.signals import post_migrate


def get_all_subclasses(cls):
    """Get all subclasses of a class, recursively.

    Used to get a list of all the implemented As.
    """
    all_subclasses = []

    for subclass in cls.__subclasses__():
        all_subclasses.append(subclass)
        all_subclasses.extend(get_all_subclasses(subclass))

    return all_subclasses


def update_As(sender=None, **kwargs):
    """Get a list of all implemented As and write them in the database.

    More precisely, each model is used to instantiate a Type, which will be used to identify As.
    """
    from app.models import A, Type

    subclasses = get_all_subclasses(A)
    for cls in subclasses:
        id = cls.identifier
        Type.objects.update_or_create(identifier=id, defaults=dict(name=cls.__name__, desc=cls.__doc__))


class MyAppConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "app"

    def ready(self):
        post_migrate.connect(update_As, sender=self)

希望这对未来有需要的 Django 编码员有所帮助!