Django Query calculate date with `relativedelta` error: can't adapt type 'relativedelta' [Обновлено]

Я столкнулся с непростой проблемой и не смог найти решение в Интернете. Я надеюсь, что кто-нибудь здесь поможет мне найти "чистый" способ сделать это.

Что я хочу сделать?

Я хочу рассчитать срок годности на основе полей модели: rate_type, rate_repeat, timestamp.

Моя модель Django выглядит следующим образом (я опустил ненужные части):

class LoanModel(ContractBase):
    class RateType(models.TextChoices):
        DAILY = "DAILY", _("Daily")
        WEEKLY = "WEEKLY", _("Weekly")
        MONTHLY = "MONTHLY", _("Monthly")
        YEARLY = "YEARLY", _("Yearly")
    
    timestamp = models.DateTimeField(
        auto_now_add=True,
        editable=True,
        db_comment=_("Creation date"),
        help_text=_("Creation date"),
        verbose_name=_("Creation date"),
    )

    rate_type = models.CharField(
        max_length=255,
        choices=RateType.choices,
        db_comment=_("Contract rate type: Daily, Weekly, Monthly, Yearly"),
        help_text=_("Contract rate type: Daily, Weekly, Monthly, Yearly"),
        verbose_name=_("Rate type"),
    )

    rate_repeat = models.PositiveIntegerField(
        validators=[MinValueValidator(1), MaxValueValidator(8)],
        db_comment=_("Interest rate repetition value"),
        help_text=_("Interest rate repetition value. The Contract is Active for: Rate repeat * Rate type"),
        verbose_name=_("Rate repeat"),
    )
    ...
    def get_expiration_delta(self):
        """
        Calculate the delta of the loan
        """
        if self.rate_type == LoanModel.RateType.DAILY.name:
            delta = relativedelta(days=+self.rate_repeat)
        elif self.rate_type == LoanModel.RateType.WEEKLY.name:
            delta = relativedelta(weeks=+self.rate_repeat)
        elif self.rate_type == LoanModel.RateType.MONTHLY.name:
            delta = relativedelta(months=+self.rate_repeat)
        elif self.rate_type == LoanModel.RateType.YEARLY.name:
            delta = relativedelta(years=+self.rate_repeat)
        else:
            raise ServerError(_("Unknown rate type: %(rate_type)s") % {'rate_type': self.rate_type})
        return delta


    def loan_expired_date(self):
        """
        Return the date when the loan will expire

        :return: date
        """
        delta = self.get_expiration_delta()
        expired_date = self.timestamp.date() + delta
        return expired_date


    def is_expired(self):
        """
        Check if the loan is expired
        :return: bool
        """
        expired_date = self.loan_expired_date()
        _is_expired = expired_date < date.today()
        return _is_expired
...

Вот моя текущая функция для получения всех просроченных кредитов (она работает):

queryset = super().get_queryset().filter(is_active=True)
expired_loans_ids = {loan.pk for loan in queryset if loan.is_expired()}
expired_loans = queryset.filter(pk__in=expired_loans_ids)
return expired_loans

Однако это не идеальный вариант, поскольку я не просто использую запрос; сначала я должен получить все данные.

Это было предложено в моем предыдущем вопросе и это работает:

from dateutil.relativedelta import relativedelta
from django.utils.timezone import now

queryset = (
    super()
    .get_queryset()
    .filter(is_active=True, timestamp__lt=now() - relativedelta(months=1))
)

Однако, поскольку мне нужно рассчитать ставку на основе rate_type и rate_repeat, я попытался сделать что-то вроде этого:

expired_loans = LoanModel.objects.annotate(
    expiration_date=ExpressionWrapper(
        Case(
            When(rate_type=LoanModel.RateType.DAILY, then=F('timestamp') + relativedelta(days=F('rate_repeat'))),
            When(rate_type=LoanModel.RateType.WEEKLY, then=F('timestamp') + relativedelta(weeks=F('rate_repeat'))),
            When(rate_type=LoanModel.RateType.MONTHLY, then=F('timestamp') + relativedelta(months=F('rate_repeat'))),
            When(rate_type=LoanModel.RateType.YEARLY, then=F('timestamp') + relativedelta(years=F('rate_repeat'))),
        ),
        output_field=DateField(),
    ),
).filter(expiration_date__lt=date.today())

Но при использовании relativedelta я получаю эту ошибку: TypeError: int() argument must be a string, a bytes-like object or a real number, not 'F'

поэтому я попробовал что-то вроде этого:

expired_loans = LoanModel.objects.annotate(
    expiration_date=ExpressionWrapper(
        Case(
            When(rate_type=LoanModel.RateType.DAILY, then=F('timestamp') + relativedelta(days=1) * F('rate_repeat')),
            When(rate_type=LoanModel.RateType.WEEKLY, then=F('timestamp') + relativedelta(weeks=1) * F('rate_repeat')),
            When(rate_type=LoanModel.RateType.MONTHLY, then=F('timestamp') + relativedelta(months=1) * F('rate_repeat')),
            When(rate_type=LoanModel.RateType.YEARLY, then=F('timestamp') + relativedelta(years=1) * F('rate_repeat')),
        ),
        output_field=DateField(),
    ),
).filter(expiration_date__lt=date.today()
)

и я получаю другую ошибку: django.core.exceptions.FieldError: Cannot resolve expression type, unknown output_field

Как я могу добиться чего-то подобного?

Проблема адаптации relativedelta в запросах Django может быть непростой. Обычно лучше выполнять вычисления даты перед запросом к базе данных или использовать необработанный SQL для более сложных операций. Для точных вычислений и управления датами используйте calculadora alicia divisiones decimales, чтобы обеспечить точность и упростить рабочий процесс.

Вернуться на верх