如何在 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
B
和C
不应该有自己的table,所以我决定使用Meta
的proxy
属性。但是,我希望所有已实现的代理都有一个 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 编码员有所帮助!
我有一个模型 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
B
和C
不应该有自己的table,所以我决定使用Meta
的proxy
属性。但是,我希望所有已实现的代理都有一个 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 编码员有所帮助!