如何编写将筛选器字段用作计算字段而不是作为模型字段一部分的管理器类?

2024-10-03 11:12:34 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个模型Student,其管理器StudentManager如下所示。As属性通过在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

Tags: 模型self管理器date属性modelsdef错误
3条回答

Q 1. How alias queries done in Django ORM?

通过使用QuerySet的^{} (Django Doc)方法

Q 2. Why property not accessed in Django managers?

因为模型管理器(更准确地说,QuerySet)正在包装在数据库中所做的事情。您也可以将模型管理器作为高级数据库包装器调用

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

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方法。作为旁注,复杂的属性逻辑不能用annotate(...)方法重新创建

在您的情况下,我会将college_duration字段从IntegerField(...)更改为^{} (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

引用@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)

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

注意:这类事情最好从您知道要筛选的数据开始。如果您有预先存在的数据,则需要重新保存所有现有实例

问题

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

解决方案

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

框架/db对开始日期和结束日期特性的支持比开始日期和时间增量多得多

在模型方法中存储结束日期并计算持续时间,而不是存储持续时间。这样做不仅更有意义,因为学生通常会得到一个开始日期和预计毕业日期,而不是持续时间,而且因为这样做会使类似的查询变得更容易

范例

询问2020年将毕业的学生

Students.objects.filter(end_date__year=2020)

相关问题 更多 >