我有一个模型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__
运行方式超出了必要。理想情况下,这种机制只会在迁移时发生。
所以你有它!我希望问题陈述是有道理的。
感谢您阅读本文,期待收到您的来信;即使您有其他事情要做,我也祝您度过愉快的一天:)