如何从 Django 中的另一个模型字段更新模型字段?
How can i update the field of a model from another model field in django?
我有两个模型。带有字段 'scores' 的 DepartmentGoal 和带有字段 'scores' 和外国 departmentgoal 的 Task。
我想要的是;如果我将分数分配给 DepartmentGoal,分配给 Task 的分数应该从 DepartmentGoal 的分数中减去,这意味着任何数量的 Task 分数实例加起来应该等于单个 DepartmentGoal 实例的分数。
我只需要有关如何实现它的线索。
这是模型
class DepartmentGoal(models.Model):
name = models.TextField(max_length=150, null=True)
score = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
class Task(models.Model):
name = models.CharField(max_length=300, null=True)
departmentgoal = models.ForeignKey(DepartmentGoal, on_delete=models.CASCADE, related_name='departgoal', null=True, blank=True)
score = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
这是表格
class DepartmentGoalForm(ModelForm):
class Meta:
model = DepartmentGoal
fields = (
('name'),
('score'),
)
class TaskForm(ModelForm):
class Meta:
model = Task
fields = [
'departmentgoal',
'name',
'score',
]
这是我的实现
class Task(models.Model):
name = models.CharField(max_length=300, null=True)
departmentgoal = models.ForeignKey(DepartmentGoal, on_delete=models.CASCADE, related_name='departgoal', null=True, blank=True)
score = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def save(self, *args, **kwargs):
goal = DepartmentGoal.objects.get(id=self.departmentgoal.id)
goal.scores -= self.scores
goal.save()
super(Task, self).save(*args, **kwargs)
我现在的问题是,如果 departmentgoals scores 已用完,即变成 0,用户仍然可以向任务添加新的任务分数,这会将 departmentgoals scores 的值更新为负分数。这是我要防止的行为。如果 departmentgoal 分数的值达到零,用户应该无法添加更多任务和任务分数
之前的回复
我要解决这个问题的方法只是将任务分数公开为可编辑,并在保存或更新 Task
实例时,更新关联 DepartmentGoal
的 score
。我不允许编辑 DepartmentGoal
分数的原因是将更改传播到相关任务会很困难。
想象一下,如果您的 DepartmentGoal
分数为 10,并且它有两个相关任务:
- 任务 1 - 目前,分数设置为 7
- 任务 2 - 目前,分数设置为 3
现在,如果将 DepartmentGoal
分数更新为 13,如何将更改传播到任务?任务 1 的分数是否增加 2,任务 2 的分数增加 1?每个任务的得分是否增加了相同的数量(在这种情况下意味着每个任务 +1.5)?
因此,仅允许编辑任务分数并将更改传播回 DepartmentGoal
,您至少可以确信 DepartmentGoal
分数将准确反映任务总和相关 Task
分数。毕竟,根据您的评论,您同意 DepartmentGoal
分数是一个 计算字段 。
所以解决方法很简单。您可以覆盖 Task
模型的 save
方法,或使用 post-保存信号。我将使用前一种方法进行演示,但如果您选择使用 post-保存信号,它会很相似。
class Task(models.Model):
name = models.CharField(max_length=300, null=True)
departmentgoal = models.ForeignKey(
DepartmentGoal,
on_delete=models.CASCADE,
related_name='departgoal',
null=True,
blank=True)
score = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# post-save, update the associated score of the `DepartmentGoal`
# 1. grab associated `DepartmentGoal`
goal = DepartmentGoal.objects.get(id=self.departmentgoal.id)
# 2. sum all task scores of the `DeparmentGoal`
# Note: I'm using `departgoal` which is the `related_name` you set on
# the foreign key. I would rename this to `tasks` since the `related_name`
# is the reference back to the model from the foreign key.
sum = goal.departgoal.values("departmentgoal") \
.annotate(total=models.Sum("score")) \
.values_list("total", flat=True)[0]
# 3. update score of `DepartmentGoal` with the calculated `sum`
goal.score = sum
goal.save(update_fields=["score"])
这只是一个最小的可行示例。显然,可以对 post-save 挂钩进行一些优化,例如检查任务的 score
是否已更改,但这需要使用字段跟踪器,例如 [=32] 提供的跟踪器=].
补充说明:
您还可以使用 property
方法,您不需要 运行 任何 post-保存挂钩,但 python 计算总和调用 属性 属性时的分数。这样做的好处是您在保存任务后不需要进行任何计算(因此性能优化)。但是,缺点是您将无法在 Django 查询集中使用属性,因为查询集使用的是字段,而不是属性。
class DepartmentGoal(models.Model):
name = models.TextField(max_length=150, null=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
@property
def score(self):
if self.departgoal.count() > 0:
return (
self.departgoal.values("departmentgoal")
.annotate(total=models.Sum("score"))
.values_list("total", flat=True)[0]
)
return 0
更新回复
这是您的要求:
- 预先定义 DepartmentGoal 的分数。
- 任何具有给定分数的新任务都会减少 DepartmentGoal 的预定义分数。
- 一旦预定义的分数用尽,则不应允许为该 DepartmentGoal 执行任何其他任务。
- 此外,对任务分数的任何修改不应导致总任务分数超过预定义分数。
解决方案
- 在您的 DepartmentGoal 模型中,定义一个名为
score
的字段。这是您预先定义分数的字段,是必填字段。
- 在您的任务模型中,添加一个
clean
方法来验证分数。 clean
方法将由您的 ModelForm
. 自动调用
- 返回您的 DepartmentGoal 模型,添加一个
clean
方法来验证分数,以防用户计划修改目标分数。如果目标已经有相关任务,这可以确保分数不会低于任务总和。
from django.core.exceptions import ValidationError
class DepartmentGoal(models.Model):
name = models.TextField(max_length=150, null=True)
score = models.IntegerField() # 1
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
# 3
def clean(self):
# calculate all contributed scores from tasks
if self.departgoal.count() > 0:
task_scores = self.departgoal.values("departmentgoal") \
.annotate(total=models.Sum("score")) \
.values_list("total", flat=True)[0]
else:
task_scores = 0
# is new score lower than `task_scores`
if self.score < task_scores:
raise ValidationError({"score": "Score not enough"})
class Task(models.Model):
name = models.CharField(max_length=300, null=True)
departmentgoal = models.ForeignKey(
DepartmentGoal,
on_delete=models.CASCADE,
related_name='departgoal',
null=True,
blank=True)
score = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
# 2
def clean(self):
# calculate contributed scores from other tasks
other_tasks = Task.objects.exclude(pk=self.pk) \
.filter(departmentgoal=self.departmentgoal)
if other_tasks.count() > 0:
contributed = (
other_tasks.values("departmentgoal")
.annotate(total=models.Sum("score"))
.values_list("total", flat=True)[0]
)
else:
contributed = 0
# is the sum of this task's score and `contributed`
# greater than DeparmentGoal's `score`
if self.score and self.score + contributed > self.departmentgoal.score:
raise ValidationError({"score": "Score is too much"})
我有两个模型。带有字段 'scores' 的 DepartmentGoal 和带有字段 'scores' 和外国 departmentgoal 的 Task。
我想要的是;如果我将分数分配给 DepartmentGoal,分配给 Task 的分数应该从 DepartmentGoal 的分数中减去,这意味着任何数量的 Task 分数实例加起来应该等于单个 DepartmentGoal 实例的分数。
我只需要有关如何实现它的线索。
这是模型
class DepartmentGoal(models.Model):
name = models.TextField(max_length=150, null=True)
score = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
class Task(models.Model):
name = models.CharField(max_length=300, null=True)
departmentgoal = models.ForeignKey(DepartmentGoal, on_delete=models.CASCADE, related_name='departgoal', null=True, blank=True)
score = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
这是表格
class DepartmentGoalForm(ModelForm):
class Meta:
model = DepartmentGoal
fields = (
('name'),
('score'),
)
class TaskForm(ModelForm):
class Meta:
model = Task
fields = [
'departmentgoal',
'name',
'score',
]
这是我的实现
class Task(models.Model):
name = models.CharField(max_length=300, null=True)
departmentgoal = models.ForeignKey(DepartmentGoal, on_delete=models.CASCADE, related_name='departgoal', null=True, blank=True)
score = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def save(self, *args, **kwargs):
goal = DepartmentGoal.objects.get(id=self.departmentgoal.id)
goal.scores -= self.scores
goal.save()
super(Task, self).save(*args, **kwargs)
我现在的问题是,如果 departmentgoals scores 已用完,即变成 0,用户仍然可以向任务添加新的任务分数,这会将 departmentgoals scores 的值更新为负分数。这是我要防止的行为。如果 departmentgoal 分数的值达到零,用户应该无法添加更多任务和任务分数
之前的回复
我要解决这个问题的方法只是将任务分数公开为可编辑,并在保存或更新 Task
实例时,更新关联 DepartmentGoal
的 score
。我不允许编辑 DepartmentGoal
分数的原因是将更改传播到相关任务会很困难。
想象一下,如果您的 DepartmentGoal
分数为 10,并且它有两个相关任务:
- 任务 1 - 目前,分数设置为 7
- 任务 2 - 目前,分数设置为 3
现在,如果将 DepartmentGoal
分数更新为 13,如何将更改传播到任务?任务 1 的分数是否增加 2,任务 2 的分数增加 1?每个任务的得分是否增加了相同的数量(在这种情况下意味着每个任务 +1.5)?
因此,仅允许编辑任务分数并将更改传播回 DepartmentGoal
,您至少可以确信 DepartmentGoal
分数将准确反映任务总和相关 Task
分数。毕竟,根据您的评论,您同意 DepartmentGoal
分数是一个 计算字段 。
所以解决方法很简单。您可以覆盖 Task
模型的 save
方法,或使用 post-保存信号。我将使用前一种方法进行演示,但如果您选择使用 post-保存信号,它会很相似。
class Task(models.Model):
name = models.CharField(max_length=300, null=True)
departmentgoal = models.ForeignKey(
DepartmentGoal,
on_delete=models.CASCADE,
related_name='departgoal',
null=True,
blank=True)
score = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# post-save, update the associated score of the `DepartmentGoal`
# 1. grab associated `DepartmentGoal`
goal = DepartmentGoal.objects.get(id=self.departmentgoal.id)
# 2. sum all task scores of the `DeparmentGoal`
# Note: I'm using `departgoal` which is the `related_name` you set on
# the foreign key. I would rename this to `tasks` since the `related_name`
# is the reference back to the model from the foreign key.
sum = goal.departgoal.values("departmentgoal") \
.annotate(total=models.Sum("score")) \
.values_list("total", flat=True)[0]
# 3. update score of `DepartmentGoal` with the calculated `sum`
goal.score = sum
goal.save(update_fields=["score"])
这只是一个最小的可行示例。显然,可以对 post-save 挂钩进行一些优化,例如检查任务的 score
是否已更改,但这需要使用字段跟踪器,例如 [=32] 提供的跟踪器=].
补充说明:
您还可以使用 property
方法,您不需要 运行 任何 post-保存挂钩,但 python 计算总和调用 属性 属性时的分数。这样做的好处是您在保存任务后不需要进行任何计算(因此性能优化)。但是,缺点是您将无法在 Django 查询集中使用属性,因为查询集使用的是字段,而不是属性。
class DepartmentGoal(models.Model):
name = models.TextField(max_length=150, null=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
@property
def score(self):
if self.departgoal.count() > 0:
return (
self.departgoal.values("departmentgoal")
.annotate(total=models.Sum("score"))
.values_list("total", flat=True)[0]
)
return 0
更新回复
这是您的要求:
- 预先定义 DepartmentGoal 的分数。
- 任何具有给定分数的新任务都会减少 DepartmentGoal 的预定义分数。
- 一旦预定义的分数用尽,则不应允许为该 DepartmentGoal 执行任何其他任务。
- 此外,对任务分数的任何修改不应导致总任务分数超过预定义分数。
解决方案
- 在您的 DepartmentGoal 模型中,定义一个名为
score
的字段。这是您预先定义分数的字段,是必填字段。 - 在您的任务模型中,添加一个
clean
方法来验证分数。clean
方法将由您的ModelForm
. 自动调用
- 返回您的 DepartmentGoal 模型,添加一个
clean
方法来验证分数,以防用户计划修改目标分数。如果目标已经有相关任务,这可以确保分数不会低于任务总和。
from django.core.exceptions import ValidationError
class DepartmentGoal(models.Model):
name = models.TextField(max_length=150, null=True)
score = models.IntegerField() # 1
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
# 3
def clean(self):
# calculate all contributed scores from tasks
if self.departgoal.count() > 0:
task_scores = self.departgoal.values("departmentgoal") \
.annotate(total=models.Sum("score")) \
.values_list("total", flat=True)[0]
else:
task_scores = 0
# is new score lower than `task_scores`
if self.score < task_scores:
raise ValidationError({"score": "Score not enough"})
class Task(models.Model):
name = models.CharField(max_length=300, null=True)
departmentgoal = models.ForeignKey(
DepartmentGoal,
on_delete=models.CASCADE,
related_name='departgoal',
null=True,
blank=True)
score = models.IntegerField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True, null=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
# 2
def clean(self):
# calculate contributed scores from other tasks
other_tasks = Task.objects.exclude(pk=self.pk) \
.filter(departmentgoal=self.departmentgoal)
if other_tasks.count() > 0:
contributed = (
other_tasks.values("departmentgoal")
.annotate(total=models.Sum("score"))
.values_list("total", flat=True)[0]
)
else:
contributed = 0
# is the sum of this task's score and `contributed`
# greater than DeparmentGoal's `score`
if self.score and self.score + contributed > self.departmentgoal.score:
raise ValidationError({"score": "Score is too much"})