如何使用Postgres在Django中计算从现在到将来的某个日期之间的每月剩余每月重复日期数

问题描述 投票:0回答:1

我有一个带有Django Rest Framework的Django应用程序,该应用程序将记录存储在Postgres中。Vehicle模型具有代表财务协议的最终支付日期的end_date DateField和代表支付金额的monthly_payment FloatField。财务付款每月一次,与最后一次付款在同一天(例如,如果end_date为25/01/2020,则从现在到2020年1月25日,包括当月的每个月25日进行付款) 。)

我有一个ListVehicle ListCreateAPIView,它返回车辆记录的分页列表。我正在使用自定义PageNumberPagination类返回data对象以及results数组,该对象是通过汇总Vehicle模型中的某些字段而填充的。我想在此data对象中包含一个字段,该字段包含要对数据库中所有Vehicle实体支付的剩余总金额。

我尝试使用模型中的@property字段来计算每个Vehicle的总剩余金额,但是您can't aggregate over calculated properties(至少没有使用queryset.aggregate),因此以下解决方案无效:

    @property
    def remaining_balance(self):
        return max(self.remaining_monthly_payments * self.monthly_payment, 0)

    @property
    def remaining_monthly_payments(self):
        now = datetime.datetime.now()
        end = self.end_date

        months = (end.year - now.year) * 12
        months -= now.month + 1
        months += end.month

        today = now.day
        final_day = end.day

        if today < final_day:
            months += 1

        return months

[我还尝试过使用分页类中的ExpressionWrapper,首先用Vehicle字段注释每个time_remaining,然后通过从remaining_balance中提取月份并将其乘以该注释来计算time_remaining字段由monthly_payment。然后,remaining_balance被聚合。

class VehicleFinancePagination(pagination.PageNumberPagination):
    def paginate_queryset(self, queryset, request, view=None):
        duration = ExpressionWrapper(F('end_date') - Now(), output_field=fields.DurationField())
        queryset = queryset.annotate(duration=duration)
        queryset = queryset.annotate(remaining_balance=ExpressionWrapper(ExtractMonth('duration') * F('monthly_payment'), output_field=FloatField()))

        self.total_remaining_balance = queryset.aggregate(total_remaining_balance=Sum('remaining_balance'))[
            "total_remaining_balance"]
        return super(VehicleFinancePagination, self).paginate_queryset(queryset, request, view)

    def get_paginated_response(self, data):
        paginated_response = super(VehicleFinancePagination, self).get_paginated_response(data)
        paginated_response.data['total_remaining_balance'] = self.total_remaining_balance

        return paginated_response


我已经尝试过这种样式的注释的几种组合。 (包括在一个注释中进行整个计算)。每次返回0。如果我使用ExtractDay而不是ExtractMonth,我会得到一个值,所以我认为这里的问题是ExtractMonthDateField获取一年中的月份,而不是DurationField中完整月份的数量]就像我希望的那样,尽管this answer会建议否则

另一项无效的方法是将Vehicle中的剩余余额/剩余月份保存起来。每月付款日期一过,Vehicle就会过期,直到下一次保存为止,因此,合计总数将不正确。

我的方法有误吗?是否可以实现我要使用自定义数据库查询执行的操作,如果可以,那么这是最佳方法吗?

使用PostgreSQL 10.10,Django 2.2,DRF 3.9.4和Python 3.6

python django postgresql django-models django-rest-framework
1个回答
0
投票

以下内容应为您提供所需的注释

PostgreSQL age函数将为您提供一个间隔,从该间隔我们可以获取月份数。我们可以定义一个定制的数据库函数:

class Age(Func):
    function = 'AGE'

然后,我们可以链接一些注释以计算月份数。在此示例中,间隔将类似于1 year 2 mon 3 days 00:00:00ExtractMonth将返回2,因此我们还需要提取年份并将其乘以12:

queryset.annotate(
    diff=Age(F('end_date'), datetime.date.today())
).annotate(
    months=ExtractYear('diff') * 12 + ExtractMonth('diff')
)

然后,我们可以链接进一步的注释,在其中我们可以使用先前的注释计算剩余余额。我们必须使用ExpressionWrapper并将其强制转换为浮点数,因为monthsmonthly_payment是不同的类型:

queryset.annotate(
    diff=Age(F('end_date'), datetime.date.today())
).annotate(
    months=ExtractYear('diff') * 12 + ExtractMonth('diff')
).annotate(
    remaining_balance=ExpressionWrapper(F('months') * F('monthly_payment'), output_field=models.FloatField())
)
© www.soinside.com 2019 - 2024. All rights reserved.