问题描述
我有两个模型。 DepartmentGoal 有一个字段“scores”,Task 也有一个字段“scores”和一个外部部门目标。
我想要的是;如果我将分数分配给 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,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)
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)
我现在的问题是,如果部门目标分数用尽,即变为 0,用户仍然可以向任务添加新的任务分数,这会将部门目标分数的值更新为负分数。这是我想要防止的行为。如果部门目标分数值为零,用户应该无法添加更多任务和任务分数
解决方法
之前的回复
我将采用的方法是仅将任务分数公开为可编辑,并在保存或更新 Task
实例时,更新关联 score
的 DepartmentGoal
。我不允许编辑 DepartmentGoal
分数的原因是因为将更改传播到相关任务会很困难。
想象一下,如果您的 DepartmentGoal
得分为 10 并且它有两个相关任务:
- 任务 1 - 当前,分数设置为 7
- 任务 2 - 当前,分数设置为 3
现在,如果您将 DepartmentGoal
分数更新为 13,您如何将更改向下传播到任务?任务 1 的分数增加 2,任务 2 的分数增加 1 吗?每项任务的分数是否增加了相等的数量(在这种情况下,这意味着每项任务 +1.5)?
因此,通过只允许编辑任务分数并将更改传播回 DepartmentGoal
,您至少可以确信 DepartmentGoal
分数将准确反映相关 { {1}} 分。毕竟,根据您的评论,您同意 Task
分数是一个计算字段。
所以解决方案非常简单。您可以覆盖 DepartmentGoal
模型的 Task
方法,或使用保存后信号。我将使用前一种方法进行演示,但如果您选择使用后保存信号,则情况类似。
save
这只是一个最小可行的例子。显然,可以对保存后挂钩进行一些优化,例如检查任务的 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"])
是否已更改,但这需要使用字段跟踪器,例如 django-model-utils 提供的跟踪器。
附加说明:
您还可以使用 score
方法,在这种方法中您不需要运行任何保存后挂钩,但在调用 property 属性时让 python 计算分数的总和。这样做的好处是您无需在保存任务后进行任何计算(因此可以优化性能)。但是,缺点是您将无法在 django 查询集中使用属性,因为查询集使用字段,而不是属性。
property
更新回复
以下是您的要求:
- 预先定义 DepartmentGoal 的得分。
- 任何具有给定分数的新任务都会减少 DepartmentGoal 的预定义分数。
- 在用完预定义的分数后,不应允许为该 DepartmentGoal 执行任何其他任务。
- 此外,对任务分数的任何修改都不应导致超过预定义分数的总任务分数。
解决方案
- 在您的 DepartmentGoal 模型中,定义一个名为
class DepartmentGoal(models.Model): name = models.TextField(max_length=150,null=True) created_at = models.DateTimeField(auto_now_add=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
的字段。这是您预先定义分数的字段,并且是必填字段。 - 在您的任务模型中,添加一个
clean
方法来验证分数。score
方法将自动由您的clean
调用。 - 回到您的 DepartmentGoal 模型,添加一个
ModelForm
方法来验证分数,以防用户计划修改目标分数。如果目标已经有关联的任务,这可以确保分数不会低于任务总和。
clean