我有一个带有经理StudentManager
的模型Student
,如下所示。由于属性通过在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
Q 1.别名查询如何在 Django ORM 中完成?
通过使用annotate(...)
--(Django Doc)或alias(...)
(Django 3.2中的新功能),如果您仅将该值用作过滤器。
问 2.为什么在 Django 管理器中无法访问属性?
因为模型管理器(更准确地说,QuerySet
)正在包装数据库中正在完成的事情。您也可以将模型管理器作为高级数据库包装器调用。
但是,属性college_end_date
仅在模型类中定义,数据库不知道它,因此会出现错误。
问题 3.如何编写管理器以根据模型中不存在但可以使用模型中存在的字段计算的字段筛选记录?
使用annotate(...)
方法是正确的 Django 方法。作为旁注,不能使用annotate(...)
方法重新创建复杂的属性逻辑。
在您的情况下,我会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()
注意:
DurationField(...)
将在PostgreSQL中按预期工作,此实现将在PSQL中按原样工作。如果您使用任何其他数据库,您可能会遇到问题,如果是这样,您可能需要有一个"数据库函数",该函数在与您的特定数据库对应的日期时间和持续时间数据集上运行。就个人而言,我喜欢这个解决方案,
引用@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)
现在,您可以在数据库中搜索它。
注意:这种事情最好从一开始就对你知道要过滤的数据进行。如果您有预先存在的数据,则需要重新保存所有现有实例。
问题
您正在尝试查询数据库中不存在的行。此外,Django ORM 不会将属性识别为要注册的字段。
溶液
对您的问题的直接回答是创建注释,随后可以对其进行查询。但是,我会重新考虑您的学生表设计,因为它引入了不必要的复杂性和维护开销。
对开始日期、结束日期特性的框架/数据库支持比开始日期、时间增量要多得多。
在模型方法中存储end_date并计算持续时间,而不是存储持续时间。这不仅更有意义,因为通常会为学生提供开始日期和估计的毕业日期而不是持续时间,而且还因为它会使此类查询变得更加容易。
例
查询哪些学生将在2020年毕业。
Students.objects.filter(end_date__year=2020)