【问题标题】:How can i update the field of a model from another model field in django?如何从 django 中的另一个模型字段更新模型的字段?
【发布时间】:2021-07-24 01:48:37
【问题描述】:

我有两个模型。 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, 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)

我现在的问题是,如果部门目标分数用尽,即变为 0,用户仍然可以向任务添加新的任务分数,这会将部门目标分数的值更新为负分数。这是我要防止的行为。如果部门目标分数的值达到零,用户应该无法添加更多的任务和任务分数

【问题讨论】:

  • 那么如果会有多个Task实例...那么您打算从哪个Task中减去分数...??
  • 如果我理解正确,您希望 DepartmentGoalscore 是一个计算字段,它是关联的 score(s) 的总和 Task(s) ?
  • @Scratch'N'purr。是的。
  • @Pratab 一个任务分数的多个实例应该总结为一个部门目标。如果设定了目标,例如建造房子,我为实现这一目标设定了 10 分。子目标也称为任务,例如绘制建筑计划 = 5。建造房子 = 3。将设置屋顶房子 = 2。这些得分将加起来达到目标​​的分数,即 10。这是我在过去 3 天不安分的夜晚一直在努力实现的目标。 ??????
  • 我希望能够使用表格设置目标分数和任务分数。

标签: django foreign-keys task django-model-field


【解决方案1】:

以前的回复

我要解决的方法是将任务分数公开为可编辑,并在保存或更新 Task 实例时,更新关联的 DepartmentGoalscore。我不允许编辑DepartmentGoal 分数的原因是因为将更改传播到相关任务会很困难。

想象一下,如果您的 DepartmentGoal 得分为 10,并且它有两个关联的任务:

  1. 任务 1 - 目前,分数设置为 7
  2. 任务 2 - 目前,分数设置为 3

现在,如果您将DepartmentGoal 分数更新为 13,您如何将更改传播到任务?任务 1 的分数增加 2,任务 2 的分数增加 1?每个任务的分数是否增加了相等的量(在这种情况下,这意味着每个任务+1.5)?

因此,仅允许编辑任务分数并将更改传播回DepartmentGoal,您至少可以确信DepartmentGoal 分数将准确反映相关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"])

这只是一个最小的可行示例。显然,可以对保存后挂钩进行一些优化,例如检查任务的score 是否已更改,但这需要使用诸如django-model-utils 提供的字段跟踪器。

补充说明:

您还可以使用property 方法,您不需要运行任何保存后挂钩,而是在调用属性属性时让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

更新响应

这是您的要求:

  1. 预先定义 DepartmentGoal 的分数。
  2. 任何具有给定分数的新任务都会降低 DepartmentGoal 的预定义分数。
  3. 一旦用完预定义的分数,就不应允许对该 DepartmentGoal 执行任何其他任务。
  4. 此外,任务分数的任何修改都不应使总任务分数超过预定义分数。

解决方案

  1. 在您的 DepartmentGoal 模型中,定义一个名为 score 的字段。这是您预先定义分数的字段,并且是必填字段。
  2. 在您的任务模型中,添加一个clean 方法来验证分数。 clean 方法将由您的ModelForm 自动调用。
  3. 回到您的 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"})

【讨论】:

  • 感谢您的及时回复@Scratch'N'purr。您的解决方案非常接近。它总结了任务,但更新了 DepartmentGoal。我不想更新 DepartmentGoal。如果 DepartmentGoal 分数为 10。如果将任务分数相加,它应该等于 10。例如,departmentgoal = 10,task1 = 7,task2 = 3。所以,task1 + task2 = departmentgoal。哪个是 10,应该保持 10。我们可以有一个条件语句来确保几个 Tasks 分数的总和必须等于固定的 DepartmentGoal 分数吗?
  • 嗨@Scratch'N'purr,我已经编辑了主要问题。请再次检查。谢谢。
  • @ThaddeausIorbee 编辑了我的新回复
  • 嗨@Scratch'N'purr,我刚刚实现了你的代码,但它显示了这个错误'异常值:列表索引超出范围'。你知道它为什么显示它吗?
  • @ThaddeausIorbee 这意味着查询集除了任务本身之外没有返回其他任务。我会更新我的答案。
猜你喜欢
  • 1970-01-01
  • 2014-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-19
  • 2020-08-08
  • 2019-03-11
  • 2023-02-24
相关资源
最近更新 更多