如何修改 .save() 上的 Django 模型字段,其值取决于传入的更改?
How to modify a Django Model field on `.save()` whose value depends on the incoming changes?
我在多个相关模型中有字段,其值完全来自正在保存的模型中的其他字段和相关模型中的字段。我想自动维护它们的价值,使它们始终 current/valid,所以我写了一个基础 class,每个模型都从中继承。它覆盖 .save()
和 .delete()
.
它几乎可以正常工作,除非通过更改名为 Infusate
和 Tracer
的模型之间的 M:M 关系的 through
模型触发多个更新( through
模型命名为 InfusateTracer
)。因此,例如,我有一个创建 2 InfusateTracer
模型记录的测试,它会触发对 Infusate
:
的更新
glu_t = Tracer.objects.create(compound=glu)
c16_t = Tracer.objects.create(compound=c16)
io = Infusate.objects.create(short_name="ti")
InfusateTracer.objects.create(infusate=io, tracer=glu_t, concentration=1.0)
InfusateTracer.objects.create(infusate=io, tracer=c16_t, concentration=2.0)
print(f"Name: {infusate.name}")
Infusate.objects.get(name="ti{C16:0-[5,6-13C5,17O1];glucose-[2,3-13C5,4-17O1]}")
save()
覆盖如下所示:
def save(self, *args, **kwargs):
# Set the changed value triggering this update so that the derived value of the automatically updated field reflects the new values:
super().save(*args, **kwargs)
# Update the fields that change due to the above change (if any)
self.update_decorated_fields()
# Note, I cannot call save again because I get a duplicate exception, so `update_decorated_fields` uses `setattr`:
# super().save(*args, **kwargs)
# Percolate changes up to the parents (if any)
self.call_parent_updaters()
自动维护的字段更新在这里执行。请注意,要更新的字段、生成它们值的函数以及父级的 link 都在 get_my_updaters()
返回的全局中维护,其值来自我编写的应用于更新函数的装饰器:
def update_decorated_fields(self):
for updater_dict in self.get_my_updaters():
update_fun = getattr(self, updater_dict["function"])
update_fld = updater_dict["update_field"]
if update_fld is not None:
current_val = None
# ... brevity edit
new_val = update_fun()
setattr(self, update_fld, new_val)
print(f"Auto-updated {self.__class__.__name__}.{update_fld} using {update_fun.__qualname__} from [{current_val}] to [{new_val}]")
并且在 post 顶部的测试代码示例中,其中创建了 InfusateTracer
linking 记录,此方法对于未完全发生的更新至关重要:
def call_parent_updaters(self):
parents = []
for updater_dict in self.get_my_updaters():
update_fun = getattr(self, updater_dict["function"])
parent_fld = updater_dict["parent_field"]
# ... brevity edit
if parent_inst is not None and parent_inst not in parents:
parents.append(parent_inst)
for parent_inst in parents:
if isinstance(parent_inst, MaintainedModel):
parent_inst.save()
elif parent_inst.__class__.__name__ == "ManyRelatedManager":
if parent_inst.count() > 0 and isinstance(
parent_inst.first(), MaintainedModel
):
for mm_parent_inst in parent_inst.all():
mm_parent_inst.save()
这里是相关的有序调试输出:
Auto-updated Infusate.name using Infusate._name from [ti] to [ti{glucose-[2,3-13C5,4-17O1]}]
Auto-updated Infusate.name using Infusate._name from [ti{glucose-[2,3-13C5,4-17O1]}] to [ti{C16:0-[5,6-13C5,17O1];glucose-[2,3-13C5,4-17O1]}]
Name: ti{glucose-[2,3-13C5,4-17O1]}
DataRepo.models.infusate.Infusate.DoesNotExist: Infusate matching query does not exist.
请注意,输出 Name: ti{glucose-[2,3-13C5,4-17O1]} 不完整(即使上面的调试输出 是完整的:ti{C16:0-[5,6-13C5,17O1];葡萄糖-[2,3-13C5,4-17O1]} ).它包含创建第一个 through
记录时产生的信息:
InfusateTracer.objects.create(infusate=io, tracer=glu_t, concentration=1.0)
但随后的 through
记录创建者:
InfusateTracer.objects.create(infusate=io, tracer=c16_t, concentration=2.0)
...(虽然所有 Auto-updated 调试输出都是正确的 - 并且是我期望看到的),但不是 Infusate 记录的最终值 name
字段(应由从最后 Auto-updated 调试输出中显示的 7 个不同记录收集的值组成(1 个 Infusate 记录、2 个 Tracer 记录和 4 个 TracerLabel 记录) )...
这是因为异步执行还是因为我应该使用 setattr
以外的东西来保存更改?我已经测试了很多次,结果总是一样。
顺便说一句,我游说我们的团队甚至不要拥有这些自动维护的字段,因为它们可能会因数据库更改而变得无效,但实验室人员显然喜欢拥有它们,因为供应商就是这样命名的化合物,他们希望能够 copy/paste 在搜索等中使用它们)。
这里的问题是对如何应用更改、何时将更改用于构建新派生字段值以及何时应调用 super().save 方法的误解。
在这里,我正在创建一条记录:
io = Infusate.objects.create(short_name="ti")
与这 2 条记录相关(也正在创建):
glu_t = Tracer.objects.create(compound=glu)
c16_t = Tracer.objects.create(compound=c16)
然后,这些记录在直通模型中链接在一起:
InfusateTracer.objects.create(infusate=io, tracer=glu_t, concentration=1.0)
InfusateTracer.objects.create(infusate=io, tracer=c16_t, concentration=2.0)
我认为(错误地)我必须调用 super().save() 以便当字段值聚集在一起组成 name
字段,这些传入的更改将包含在名称中。
但是,self
对象用于检索这些值。没保存也没关系
在这一点上,将问题中提供的代码中的一些空白包括在内是很有用的。这是 Infusate
模型的一部分:
class Infusate(MaintainedModel):
id = models.AutoField(primary_key=True)
name = models.CharField(...)
short_name = models.CharField(...)
tracers = models.ManyToManyField(
Tracer,
through="InfusateTracer",
)
@field_updater_function(generation=0, update_field_name="name")
def _name(self):
if self.tracers is None or self.tracers.count() == 0:
return self.short_name
return (
self.short_name
+ "{"
+ ";".join(sorted(map(lambda o: o._name(), self.tracers.all())))
+ "}"
)
这是我推断(错误地)表示在我可以访问值之前必须保存记录的错误:
ValueError: "<Infusate: >" needs to have a value for field "id" before this many-to-many relationship can be used.
当我尝试以下版本的 save
覆盖时:
def save(self, *args, **kwargs):
self.update_decorated_fields()
super().save(*args, **kwargs)
self.call_parent_updaters()
但这真正意味着我必须测试除 self.tracers is None
之外的其他内容,以查看是否存在任何 M:M 链接。我们可以简单地检查 self.id
。如果是None
,我们可以推断self.tracers
不存在。所以这个问题的答案只是将 save
方法覆盖编辑为:
def save(self, *args, **kwargs):
self.update_decorated_fields()
super().save(*args, **kwargs)
self.call_parent_updaters()
并编辑为字段更新生成值的方法:
@field_updater_function(generation=0, update_field_name="name")
def _name(self):
if self.id is None or self.tracers is None or self.tracers.count() == 0:
return self.short_name
return (
self.short_name
+ "{"
+ ";".join(sorted(map(lambda o: o._name(), self.tracers.all())))
+ "}"
)
我在多个相关模型中有字段,其值完全来自正在保存的模型中的其他字段和相关模型中的字段。我想自动维护它们的价值,使它们始终 current/valid,所以我写了一个基础 class,每个模型都从中继承。它覆盖 .save()
和 .delete()
.
它几乎可以正常工作,除非通过更改名为 Infusate
和 Tracer
的模型之间的 M:M 关系的 through
模型触发多个更新( through
模型命名为 InfusateTracer
)。因此,例如,我有一个创建 2 InfusateTracer
模型记录的测试,它会触发对 Infusate
:
glu_t = Tracer.objects.create(compound=glu)
c16_t = Tracer.objects.create(compound=c16)
io = Infusate.objects.create(short_name="ti")
InfusateTracer.objects.create(infusate=io, tracer=glu_t, concentration=1.0)
InfusateTracer.objects.create(infusate=io, tracer=c16_t, concentration=2.0)
print(f"Name: {infusate.name}")
Infusate.objects.get(name="ti{C16:0-[5,6-13C5,17O1];glucose-[2,3-13C5,4-17O1]}")
save()
覆盖如下所示:
def save(self, *args, **kwargs):
# Set the changed value triggering this update so that the derived value of the automatically updated field reflects the new values:
super().save(*args, **kwargs)
# Update the fields that change due to the above change (if any)
self.update_decorated_fields()
# Note, I cannot call save again because I get a duplicate exception, so `update_decorated_fields` uses `setattr`:
# super().save(*args, **kwargs)
# Percolate changes up to the parents (if any)
self.call_parent_updaters()
自动维护的字段更新在这里执行。请注意,要更新的字段、生成它们值的函数以及父级的 link 都在 get_my_updaters()
返回的全局中维护,其值来自我编写的应用于更新函数的装饰器:
def update_decorated_fields(self):
for updater_dict in self.get_my_updaters():
update_fun = getattr(self, updater_dict["function"])
update_fld = updater_dict["update_field"]
if update_fld is not None:
current_val = None
# ... brevity edit
new_val = update_fun()
setattr(self, update_fld, new_val)
print(f"Auto-updated {self.__class__.__name__}.{update_fld} using {update_fun.__qualname__} from [{current_val}] to [{new_val}]")
并且在 post 顶部的测试代码示例中,其中创建了 InfusateTracer
linking 记录,此方法对于未完全发生的更新至关重要:
def call_parent_updaters(self):
parents = []
for updater_dict in self.get_my_updaters():
update_fun = getattr(self, updater_dict["function"])
parent_fld = updater_dict["parent_field"]
# ... brevity edit
if parent_inst is not None and parent_inst not in parents:
parents.append(parent_inst)
for parent_inst in parents:
if isinstance(parent_inst, MaintainedModel):
parent_inst.save()
elif parent_inst.__class__.__name__ == "ManyRelatedManager":
if parent_inst.count() > 0 and isinstance(
parent_inst.first(), MaintainedModel
):
for mm_parent_inst in parent_inst.all():
mm_parent_inst.save()
这里是相关的有序调试输出:
Auto-updated Infusate.name using Infusate._name from [ti] to [ti{glucose-[2,3-13C5,4-17O1]}]
Auto-updated Infusate.name using Infusate._name from [ti{glucose-[2,3-13C5,4-17O1]}] to [ti{C16:0-[5,6-13C5,17O1];glucose-[2,3-13C5,4-17O1]}]
Name: ti{glucose-[2,3-13C5,4-17O1]}
DataRepo.models.infusate.Infusate.DoesNotExist: Infusate matching query does not exist.
请注意,输出 Name: ti{glucose-[2,3-13C5,4-17O1]} 不完整(即使上面的调试输出 是完整的:ti{C16:0-[5,6-13C5,17O1];葡萄糖-[2,3-13C5,4-17O1]} ).它包含创建第一个 through
记录时产生的信息:
InfusateTracer.objects.create(infusate=io, tracer=glu_t, concentration=1.0)
但随后的 through
记录创建者:
InfusateTracer.objects.create(infusate=io, tracer=c16_t, concentration=2.0)
...(虽然所有 Auto-updated 调试输出都是正确的 - 并且是我期望看到的),但不是 Infusate 记录的最终值 name
字段(应由从最后 Auto-updated 调试输出中显示的 7 个不同记录收集的值组成(1 个 Infusate 记录、2 个 Tracer 记录和 4 个 TracerLabel 记录) )...
这是因为异步执行还是因为我应该使用 setattr
以外的东西来保存更改?我已经测试了很多次,结果总是一样。
顺便说一句,我游说我们的团队甚至不要拥有这些自动维护的字段,因为它们可能会因数据库更改而变得无效,但实验室人员显然喜欢拥有它们,因为供应商就是这样命名的化合物,他们希望能够 copy/paste 在搜索等中使用它们)。
这里的问题是对如何应用更改、何时将更改用于构建新派生字段值以及何时应调用 super().save 方法的误解。
在这里,我正在创建一条记录:
io = Infusate.objects.create(short_name="ti")
与这 2 条记录相关(也正在创建):
glu_t = Tracer.objects.create(compound=glu)
c16_t = Tracer.objects.create(compound=c16)
然后,这些记录在直通模型中链接在一起:
InfusateTracer.objects.create(infusate=io, tracer=glu_t, concentration=1.0)
InfusateTracer.objects.create(infusate=io, tracer=c16_t, concentration=2.0)
我认为(错误地)我必须调用 super().save() 以便当字段值聚集在一起组成 name
字段,这些传入的更改将包含在名称中。
但是,self
对象用于检索这些值。没保存也没关系
在这一点上,将问题中提供的代码中的一些空白包括在内是很有用的。这是 Infusate
模型的一部分:
class Infusate(MaintainedModel):
id = models.AutoField(primary_key=True)
name = models.CharField(...)
short_name = models.CharField(...)
tracers = models.ManyToManyField(
Tracer,
through="InfusateTracer",
)
@field_updater_function(generation=0, update_field_name="name")
def _name(self):
if self.tracers is None or self.tracers.count() == 0:
return self.short_name
return (
self.short_name
+ "{"
+ ";".join(sorted(map(lambda o: o._name(), self.tracers.all())))
+ "}"
)
这是我推断(错误地)表示在我可以访问值之前必须保存记录的错误:
ValueError: "<Infusate: >" needs to have a value for field "id" before this many-to-many relationship can be used.
当我尝试以下版本的 save
覆盖时:
def save(self, *args, **kwargs):
self.update_decorated_fields()
super().save(*args, **kwargs)
self.call_parent_updaters()
但这真正意味着我必须测试除 self.tracers is None
之外的其他内容,以查看是否存在任何 M:M 链接。我们可以简单地检查 self.id
。如果是None
,我们可以推断self.tracers
不存在。所以这个问题的答案只是将 save
方法覆盖编辑为:
def save(self, *args, **kwargs):
self.update_decorated_fields()
super().save(*args, **kwargs)
self.call_parent_updaters()
并编辑为字段更新生成值的方法:
@field_updater_function(generation=0, update_field_name="name")
def _name(self):
if self.id is None or self.tracers is None or self.tracers.count() == 0:
return self.short_name
return (
self.short_name
+ "{"
+ ";".join(sorted(map(lambda o: o._name(), self.tracers.all())))
+ "}"
)