Как рассчитать количество оставшихся ежемесячных повторяющихся дат между настоящим моментом и датой в будущем в Джанго с помощью Postgres - PullRequest
0 голосов
/ 06 ноября 2019

У меня есть приложение Django с Django Rest Framework, которое хранит записи в Postgres. Модель Vehicle имеет end_date DateField, представляющую дату окончательной оплаты финансового соглашения, и monthly_payment FloatField, представляющую сумму платежа. Финансовый платеж производится ежемесячно, в тот же день месяца, что и последний платеж (например, если end_date - 25.01.2020, платеж производится 25 числа каждого месяца с настоящего момента до 25.01.2020 года включительно). .)

У меня есть ListVehicle ListCreateAPIView, который возвращает нумерованный список записей транспортных средств. Я использую собственный класс PageNumberPagination для возврата объекта data вместе с массивом results, который заполняется путем агрегирования некоторых полей в модели Vehicle. Я хочу включить в этот объект data поле, содержащее общую оставшуюся сумму, оставшуюся для оплаты всех объектов Vehicle в базе данных.

Я пытался использовать @property поля в модели, которые вычисляют общую оставшуюся сумму для каждого Vehicle, но вы не можете агрегировать по вычисленным свойствам (по крайней мере, не с 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 s в классе нумерации страниц, чтобы сначала аннотировать каждый 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, поэтому я думаю, что проблема здесь в том, что ExtractMonth получает месяц года из DateField, но не количество полных месяцев в DurationField как я и надеялся, хотя этот ответ подсказывал бы иначе

Еще одна вещь, которая не работает, - это сохранение остатка / оставшихся месяцев с Vehicle, когда он сохранен. Как только истекает дата ежемесячного платежа, Vehicle будет устаревать до следующего сохранения, и поэтому суммарные итоги будут неверными.

Неправильный ли я подход? Можно ли добиться того, что я пытаюсь сделать, с помощью пользовательского запроса к базе данных, и если да, то будет ли это лучшим способом?

Использование PostgreSQL 10.10, Django 2.2, DRF 3.9.4 и Python 3.6

РЕДАКТИРОВАТЬ: Testcase для полноты

    @mock.patch('workshop.models.vehicle.datetime')
    def test_remaining_balance(self, mocked_datetime):
        mocked_datetime.datetime.now.return_value = datetime.datetime(2019, 11, 7, 1, 2, 3)

        Vehicle.objects.create(registration="TS01 TST", monthly_payment=500, end_date=datetime.date(2020, 3, 1)) #2000
        Vehicle.objects.create(registration="TS02 TST", monthly_payment=250, end_date=datetime.date(2020, 3, 25)) #1250
        Vehicle.objects.create(registration="TS03 TST", monthly_payment=400, end_date=datetime.date(2020, 5, 1)) #2400
        Vehicle.objects.create(registration="TS04 TST", monthly_payment=300, end_date=datetime.date(2020, 5, 25)) #2100
        Vehicle.objects.create(registration="TS03 TST", monthly_payment=400, end_date=datetime.date(2018, 5, 1)) #0
        Vehicle.objects.create(registration="TS04 TST", monthly_payment=300, end_date=datetime.date(2018, 5, 25)) #0

        url = reverse('vehicles')
        response = self.client.get(url)

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['total_remaining_balance'],  7750)

1 Ответ

2 голосов
/ 07 ноября 2019

Следующее должно дать вам нужную аннотацию

Функция PostgreSQL age даст вам интервал, из которого мы можем получить количество месяцев. Мы можем определить пользовательскую функцию БД:

class Age(Func):
    function = 'AGE'

Затем мы можем связать некоторые аннотации для вычисления количества месяцев. Интервал будет примерно таким, как 1 year 2 mon 3 days 00:00:00, и ExtractMonth вернет 2 для этого примера, поэтому нам также нужно извлечь год и умножить его на 12:

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

Затем мы можем соединить ещеаннотации, где мы рассчитываем остаток с использованием предыдущих аннотаций. Мы должны использовать ExpressionWrapper и приводить к типу с плавающей точкой, поскольку months и monthly_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())
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...