2

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

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

B并且C不应该有自己的表,所以我决定proxy使用Meta. 但是,我希望有一个包含所有已实施代理的表。特别是,我想记录每个子类的名称和描述。例如,对于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。因此,Type表应该始终是最新的。例如,如果我想对 的行为进行单元测试B,我需要使用相应的Type实例来创建 的实例B,因此Type实例已经需要在数据库中

查看Django 网站,我看到了两种方法来实现这一点:固定装置和数据迁移。夹具对于我的用例来说不够动态,因为属性实际上来自代码。这让我不得不进行数据迁移。

我试着写一个,是这样的:

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

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

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

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表不存在。update_or_create当我在开发中遇到这个问题时(我们仍处于早期阶段,因此删除数据库仍​​然是明智的),我必须将in注释掉__init_subclass__。然后我可以迁移并将其放回原处。

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

所以你有它!我希望问题陈述是有道理的。

感谢您阅读本文,期待收到您的来信;即使您有其他事情要做,我也祝您度过愉快的一天:)

4

1 回答 1

0

在 Django 专家朋友的帮助下,我用post_migrate信号解决了这个问题。我删除了update_or_createin __init_subclass,并在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 编码员有所帮助!

于 2022-03-04T17:01:07.573 回答