首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Django模型字段自动更新依赖于相关实例

Django模型字段自动更新依赖于相关实例
EN

Stack Overflow用户
提问于 2018-02-14 17:17:08
回答 1查看 1.7K关注 0票数 0

我正在寻找能够根据相关模型实例自动更新模型实例的解决方案。

看起来很简单,我到处都找过了。我是新手,所以让我知道我问错了问题,或者用错误的方式思考这个问题。

我需要Account.monthly_donations自动更新,在过去30天的所有相关捐款的总额。yearly_donations和lifetime_donations也是如此。

我还需要Account.is_subscribed来切换True如果

  • monthly_donations >= 20,或
  • yearly_donations >= 200或
  • lifetime_donations >= 10000

如果不符合要求,那就回错了。

代码语言:javascript
复制
class Account(models.Model):
    ...
    is_subscribed = models.BooleanField(
        default=False
    )
    monthly_donations = models.PositiveIntegerField(
        default=0
    )
    yearly_donations = models.PositiveIntegerField(
        default=0
    )
    lifetime_donations = models.PositiveIntegerField(
        default=0
    )


class Donation(models.Model):
    related_account = models.ForeignKey(
        Account,
        on_delete=models.CASCADE,
        related_name='donation'
    )
    amount = models.IntegerField()
    date = models.DateField()

信号是解决方案吗?但是,我不想只在新的捐款被保存时才更新总数。我需要根据捐款日期每天更新。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-02-14 19:30:10

信号不能百分之百解决你的问题。因为monthly_donationsyearly_donations依赖于当前的日期来给出正确的答案。

当代码的多个部分对相同的事件感兴趣时,信号通常是一个很好的解决方案(在您的示例中,只有Account实例对新的Donation对象感兴趣)。此外,当您无法直接访问代码时(例如第三方应用程序,或者不能修改的内置模型,如UserPermission )。

在您的示例中,您可以访问Donation模型,因此您可以重写save()方法:

代码语言:javascript
复制
class Donation(models.Model):
    # db fields ...

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)  # call the actual save method
        update_account_totals()        # execute this code every time the model is saved

但是,正如您已经提到的,您不希望只在创建新的捐赠时更新此信息。

实际上,您目前存储在Account模型中的所有信息都可以实时计算.使用更高级的QuerySet表达式,我们可以直接在数据库中完成所有工作。即使我们每次都要计算它,它也可以非常快。特别是如果你缓存结果。

这种查询称为聚合。我将使用annotate()函数为您的用例编写一个示例。annotate()函数是一个特殊的函数,它根据我们定义的标准,向结果集的每个实例“添加额外的字段”。

通常,我们在annotate()中添加的字段是某物的sum,或countaverage。在您的例子中,我们可以列出所有的Account对象,对于每个对象,我们可以注释每月捐款、年度捐赠和所有时间捐赠的总和。我们甚至可以使用这个计算字段来检查帐户是否是订阅者,使用case/when查询。

这比我们一直使用的QuerySets (比如filter()all() )要复杂一些,但我花了时间把它写下来,并给出了一些可能有助于您解决问题的注释。

PS:下面的示例使用在Django 2.0上引入的新对注解的过滤

Implementation:

代码语言:javascript
复制
import datetime

from django.utils import timezone
from django.db.models import Case, When, Q, Value, BooleanField, Sum
from django.db.models.functions import Coalesce

# first we use the `timedelta` to get refernce points
# considering now = 2018-02-14, `last_month` will be equal to 2018-01-14
# and `last_year` will be equal to 2017-02-14
last_month = timezone.now() - datetime.timedelta(days=30)
last_year = timezone.now() - datetime.timedelta(days=365)

# here we are building a sub-query using the Sum object, we are going to use it next
# to sum all the donations happened after "2018-01-14", that is, in the last 30 days
monthly_sum_expression = Sum('donation__amount', filter=Q(donation__date__gte=last_month))

# similar to the previous one, we are summing all donations that happened after "2017-02-14"
# that is, one year ago
yearly_sum_expression = Sum('donation__amount', filter=Q(donation__date__gte=last_year))

# here we are not applying any filter, so we will sum *all* donations
all_time_sum_expression = Sum('donation__amount')

# below, we are building the logic to tell if the person is a subscriber or not (based on the criteria you
# entered in your question.. monthly donations >= 20, or yearly donations >= 200, etc)
# The pipe "|" means it's an OR. the "Q" is an object that holds a database expression
# if any of those criteria are met, then it will return "True"
subscriber_condition = When(Q(monthly__gte=20) | Q(yearly__gte=200) | Q(all_time__gte=1000), then=Value(True))
subscriber_expression = Case(subscriber_condition, default=Value(False), output_field=BooleanField())

# now here we build our query. in this case we are selection *all* the accounts and for each account
# we are adding 4 extra fields calculated on the fly: monthly, yearly, all_time, and subscriber_status.
# the Coalesce function is for the cases where the account had no donation in the last 30 days
# instead of returning "None" it will return "0"
accounts = Account.objects.annotate(
    monthly=Coalesce(monthly_sum_expression, Value(0)),
    yearly=Coalesce(yearly_sum_expression, Value(0)),
    all_time=Coalesce(all_time_sum_expression, Value(0)),
    subscriber_status=subscriber_expression
)

使用:

代码语言:javascript
复制
# then in your code you can use those calculated fields just like you would if it was a regular database field
for account in accounts:
    print(account.monthly)
    print(account.yearly)
    print(account.all_time)
    print(account.subscriber_status)

这将是一个最佳解决方案。这样,您就可以确保数据始终是最新的。另一种解决方案是像您一样保留数据库字段,并在服务器上创建一些cron作业,这样它们将每天更新所有帐户,Account字段将充当计算字段的“缓存”。

然后,您可以将cron作业与我在开头提到的save()方法重写结合起来。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48792847

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档