如何编写使用过滤字段作为计算字段而不是模型字段一部分的管理器 class?

How to write manager class which use filter field as computed field not as a part of model fields?

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

计算字段不在模型字段中。尽管如此,我仍希望将其作为过滤条件。

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

问题

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

解决方案

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

与开始日期、timedelta 相比,framework/db 对开始日期、结束日期特性的支持要多得多。

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

示例

查询2020年毕业的学生

Students.objects.filter(end_date__year=2020)

引用@Willem Van Olsem 的评论:

You don't. The database does not know anything about properties, etc. So it can not filter on this. You can make use of .annotate(..) to move the logic to the database side.

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

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)

现在可以在数据库中搜索了。

注意: 这种事情最好从一开始就对您知道要过滤的数据进行。如果您有 pre-existing 数据,则需要 re-save 所有现有实例。

Q 1. How alias queries done in Django ORM?

通过使用 QuerySet

annotate(...)--(Django Doc) 方法

Q 2. Why property not accessed in Django managers?

因为模型管理器(更准确地说,QuerySet s)正在包装正在 数据库[=62= 中完成的事情].您也可以将模型管理器称为 high-level 数据库包装器。

但是,属性 college_end_date 仅在您的模型中定义 class 并且数据库不知道它,因此错误。

Q 3. How to write manager to filter records based on the field which is not in models, but can be calculated using fields present in the model?

使用 annotate(...) 方法是正确的 Django 方法。作为旁注,复杂的 属性 逻辑可能不是 re-createannotate(...)方法。

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

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

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


class StudentManager(models.Manager):

    <b>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())</b>


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 <b>(self.join_date + self.college_duration).date()</b>

注:

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

  • 个人比较喜欢,