【问题标题】:How to write manager class which use filter field as computed field not as a part of model fields?如何编写使用过滤器字段作为计算字段而不是模型字段的一部分的管理器类?
【发布时间】:2020-10-08 13:43:54
【问题描述】:

我有一个模型Student 和经理StudentManager,如下所示。由于属性通过在join_date 中添加college_duration 来给出最后日期。但是当我执行这个属性计算时运行良好,但是对于StudentManager 它给出了一个错误。如何编写使用模型字段动态计算某些字段并用于过滤记录的管理器类。

计算域不在模型域中。不过,我希望它作为过滤条件。

class StudentManager(models.Manager):

    def passed_students(self):
        return self.filter(college_end_date__lt=timezone.now())


class Student(models.Model):
   
    join_date = models.DateTimeField(auto_now_add=True)
    college_duration = models.IntegerField(default=4)

    objects = StudentManager()

    @property
    def college_end_date(self):
        last_date = self.join_date + timezone.timedelta(days=self.college_duration)
        return last_date

Django 给出的错误。当我尝试访问Student.objects.passed_students()

django.core.exceptions.FieldError: Cannot resolve keyword 'college_end_date' into field. Choices are: join_date, college_duration

【问题讨论】:

  • 你没有。数据库对属性等一无所知,因此无法对此进行过滤。您可以利用.annotate(..) 将逻辑移至数据库端。

标签: python django django-models django-managers


【解决方案1】:

Q 1.在 Django ORM 中如何进行别名查询?

通过使用QuerySetannotate(...)--(Django Doc)方法

Q 2.为什么在 Django 管理器中没有访问属性?

因为模型管理器(更准确地说,QuerySet s)正在包装数据库中正在执行的操作。您也可以将模型管理器称为高级数据库包装器。

但是,属性 college_end_date 仅在您的模型类中定义,数据库不知道它,因此会出现错误。

Q 3.如何编写管理器根据模型中没有的字段过滤记录,但可以使用模型中存在的字段计算?

使用 annotate(...) 方法是正确的 Django 方法。附带说明一下,使用 annotate(...) 方法可能无法重新创建复杂的属性逻辑。

在您的情况下,我会将 college_duration 字段从 IntegerField(...) 更改为 DurationField(...)--(Django Doc),因为它更有意义(对我而言)

稍后,将您的经理和属性更新为,

from django.db import models
from django.utils import timezone


class StudentManager(models.Manager):

    def passed_students(self):
        default_qs = self.get_queryset()
        college_end = models.ExpressionWrapper(
            models.F('join_date') + models.F('college_duration'),
            output_field=models.DateField()
        )
        return default_qs \
            .annotate(college_end=college_end) \
            .filter(college_end__lt=timezone.now().date())


class Student(models.Model):
    join_date = models.DateTimeField()
    college_duration = models.DurationField()

    objects = StudentManager()

    @property
    def college_end_date(self):
        # return date by summing the datetime and timedelta objects
        return (self.join_date + self.college_duration).date()

注意:

  • DurationField(...) 将在 PostgreSQL 中按预期工作,并且此实现将在 PSQL 中按原样工作。如果您使用任何其他数据库,您可能会遇到问题,如果是这样,您可能需要有一个“数据库函数”,它在与您的特定数据库相对应的日期时间和持续时间数据集上运行。

  • 就个人而言,我喜欢this solution

【讨论】:

  • 是的。它可以在模型上制作注释字段。但也如上所述,制作 DurationField 将是最佳实践,如果无法更改数据库,那么这是最佳解决方案。
【解决方案2】:

引用@Willem Van Olsem 的评论:

你没有。数据库对属性等一无所知,因此无法对此进行过滤。您可以使用 .annotate(..) 将逻辑移动到数据库端。

您可以执行他分享的消息,也可以将其设为自动计算的模型字段。

class StudentManager(models.Manager):

    def passed_students(self):
        return self.filter(college_end_date__lt=timezone.now())


class Student(models.Model):
   
    join_date = models.DateTimeField(auto_now_add=True)
    college_duration = models.IntegerField(default=4)
    college_end_date = models.DateTimeField()

    objects = StudentManager()

    def save(self, *args, **kwargs):
        # Add logic here
        if not self.college_end_date:
            self.college_end_date = self.join_date + timezone.timedelta(days-self.college_duration)
        return super.save(*args, **kwargs)

现在您可以在数据库中搜索它。

注意:最好从一开始就对您知道要过滤的数据执行此类操作。如果您有预先存在的数据,则需要重新保存所有现有实例。

【讨论】:

    【解决方案3】:

    问题

    您正在尝试查询数据库中不存在的行。此外,Django ORM 不会将属性识别为要注册的字段。

    解决方案

    您的问题的直接答案是创建注释,随后可以对其进行查询。但是,我会重新考虑您的 Student 表设计,因为它引入了不必要的复杂性和维护开销。

    对开始日期、结束日期特性的框架/数据库支持比开始日期、时间增量要多得多。

    不是存储持续时间,而是存储 end_date 并在模型方法中计算持续时间。这不仅更有意义,因为通常会为学生提供开始日期和估计毕业日期而不是持续时间,而且因为它会使此类查询变得更加容易。

    示例

    查询哪些学生将于 2020 年毕业。

    Students.objects.filter(end_date__year=2020)
    

    【讨论】:

      猜你喜欢
      • 2018-05-27
      • 2013-08-20
      • 2023-03-25
      • 1970-01-01
      • 2016-10-10
      • 1970-01-01
      • 2020-10-30
      • 2018-10-09
      • 1970-01-01
      相关资源
      最近更新 更多