【问题标题】:Django QuertySet.annotate() received non-expression - how to add a derived field based on model field?Django QuertySet.annotate() 收到非表达式 - 如何添加基于模型字段的派生字段?
【发布时间】:2020-07-31 00:58:05
【问题描述】:

第一次接触 Django。尝试向查询集添加注释:

class EnrollmentManager(models.Manager.from_queryset(EnrollmentCustomQuerySet)):
  COURSE_DURATION = datetime.timedelta(days=183)

  def get_queryset(self):
      """Overrides the models.Manager method"""
      lookback = make_aware(datetime.datetime.today() - self.COURSE_DURATION)
      qs = super(EnrollmentManager, self).get_queryset().annotate( \
              is_expired=(Value(True)), output_field=models.BooleanField())
      return qs

目前我只是想在返回的查询集上添加一个额外的“计算”字段,该字段被硬编码为 True,属性/字段应称为 is_expired

如果我可以让它工作,那么 Value(True) 需要是基于这个表达式的派生值:

F('enrolled') < lookback

但由于 'enrolled' 是一个数据库字段并且 lookback 是计算出来的,我将如何做到这一点?

注意

我试过这个,它执行时不会抛出错误:

qs = super(EnrollmentManager, self).get_queryset().annotate( \
         is_expired=(Value(True, output_field=models.BooleanField())))

在shell中我可以看到它:

Enrollment.objects.all()[0].is_expired -> returns True

我可以将它添加到序列化程序中:

class EnrollmentSerializer(serializers.ModelSerializer):
  is_active = serializers.SerializerMethodField()
  is_current = serializers.SerializerMethodField()
  is_expired = serializers.SerializerMethodField()
  COURSE_DURATION = datetime.timedelta(days=183)

  class Meta:
    model = Enrollment
    fields = ('id', 'is_active', 'is_current', 'is_expired')

  def get_is_expired(self, obj):
    return obj.is_expired

所以有可能...但是我如何用计算替换我的硬编码“真”?

更新

阅读documentation,它指出:

"使用提供的查询表达式列表注释 QuerySet 中的每个对象。表达式可以是简单的值、对模型(或任何相关模型)上字段的引用或聚合表达式(平均值、总和、等)已在与 QuerySet 中的对象相关的对象上计算。”

一个简单的值 - 那么,不是一个简单的 COMPUTED 值吗?

这让我觉得这是不可能的……

【问题讨论】:

    标签: django django-rest-framework django-queryset


    【解决方案1】:

    对于Case 表达式来说,这似乎是一个很好的用例。我建议尽可能熟悉these expression tools,它们非常有帮助!

    我没有对此进行测试,但它应该可以工作。我假设注册是他们第一次注册时的 tz 感知日期时间......

    from django.db.models import Case, When, Value
    
    def get_queryset(self):
        """Overrides the models.Manager method"""
        lookback = make_aware(datetime.datetime.today() - self.COURSE_DURATION)
        qs = super(EnrollmentManager, self).get_queryset().annotate(
            is_expired=Case(
                When(
                    enrolled__lt=lookback,
                    then=Value(True)
                ),
                default=Value(False),
                output_field=models.BooleanField()
            )
        )
    

    您也不必预先计算回溯变量。查看解决此问题的 ExpressionWrappers 和 this StackOverflow answer

    ExpressionWrapper(
        TruncDate(F('date1')) + datetime.timedelta(days=365),
        output_field=DateField(),
    )
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-13
      • 2018-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-14
      • 2018-11-01
      • 2011-06-11
      相关资源
      最近更新 更多