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

发布于 2025-01-11 12:35:59 字数 3170 浏览 0 评论 0原文

我有一个模型 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

BC不应该有自己的表,所以我决定使用Metaproxy属性。但是,我希望有一个包含所有已实施代理的表格。 特别是,我想记录每个子类的名称和描述。 例如,对于 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 的实例code>,因此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

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

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

所以你就拥有了!我希望问题陈述有意义。

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

I have a model A and want to make subclasses of it.

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 and C should not have their own tables, so I decided to use the proxy attirbute of Meta. However, I want there to be a table of all the implemented proxies.
In particular, I want to keep a record of the name and description of each subclass.
For example, for B, the name would be "B" and the description would be the docstring for B.
So I made another model:

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()

Now, I want it so when I create an A, I can only choose between the different subclasses of A.
Hence the Type table should always be up-to-date.
For example, if I want to unit-test the behavior of B, I'll need to use the corresponding Type instance to create an instance of B, so that Type instance already needs to be in the database.

Looking over on the Django website, I see two ways to achieve this: fixtures and data migrations.
Fixtures aren't dynamic enough for my usecase, since the attributes literally come from the code. That leaves me with data migrations.

I tried writing one, that goes something like this:

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

The problem is, I don't see how to store the identifier within the class, so that the Django Model instance can recover it.
So far, here is what I have tried:

I have tried using the fairly new __init_subclass__ construct of Python. So my code now looks like:

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

But this update_or_create fails when the database is new (e.g. during unit tests), because the Type table does not exist.
When I have this problem in development (we're still in early stages so deleting the DB is still sensible), I have to go
comment out the update_or_create in __init_subclass__. I can then migrate and put it back in.

Of course, this solution is also not great because __init_subclass__ is run way more than necessary. Ideally this machinery would only happen at migration.

So there you have it! I hope the problem statement makes sense.

Thanks for reading this far and I look forward to hearing from you; even if you have other things to do, I wish you a good rest of your day :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

独木成林 2025-01-18 12:35:59

在 Django 专家朋友的帮助下,我用 解决了这个问题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 编码人员有帮助!

With a little help from Django-expert friends, I solved this with the post_migrate signal.
I removed the update_or_create in __init_subclass, and in project/app/apps.py I added:

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)

Hope this is helpful for future Django coders in need!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文