如何在Django中维护所有代理模型的表?
我有一个模型 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
不应该有自己的表,所以我决定使用Meta
的proxy
属性。但是,我希望有一个包含所有已实施代理的表格。 特别是,我想记录每个子类的名称和描述。 例如,对于 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
在 Django 专家朋友的帮助下,我用 解决了这个问题
post_migrate
信号。我删除了
__init_subclass
中的update_or_create
,并在project/app/apps.py
中添加了:希望这对未来有需要的 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 inproject/app/apps.py
I added:Hope this is helpful for future Django coders in need!