尝试使用子查询计算时差时出错

时间:2018-08-04 09:28:58

标签: django django-models django-queryset django-orm

我跟踪人们在特定页面上花费的时间(我问了有关此问题here的几个问题)。我注册页面加载的时间(“ Enter”事件)。然后,在一个单独的模型中,我注册所有“退出”:提交表单时,页面卸载时等等。此“ ExitEvent”模型通过ForeignKey连接到EnterEvent模型,因为一个Enter事件可以并且通常确实具有多个对应的Exit事件。

EXITTYPES = [(0, 'form submitted'), (1, 'page unloaded'), (2, 'client disconnected')]


class EnterEvent(models.Model):
    page_name = models.CharField(max_length=1000)
    user = models.ForeignKey(to=User, related_name='enters')
    timestamp = models.DateTimeField()
    closed = models.BooleanField(default=False)


class ExitEvent(models.Model):
    enter_event = models.ForeignKey(to=EnterEvent, related_name='exits')
    timestamp = models.DateTimeField()
    exit_type = models.IntegerField(choices=EXITTYPES)

要计算用户在页面上花费的时间,我使用以下代码可以正常工作:

def get_time_per_page(user, page_name):
    earliest = ExitEvent.objects.filter(enter_event=OuterRef('pk')).order_by('timestamp')
    delta = timedelta(days=1)

    a = EnterEvent.objects.filter(closed=True,
                                  user=user,
                                  page_name=page_name).annotate(
        earliest_exit=Subquery(earliest.values('timestamp')[:1]),
    ).values()
    sum_diff = sum([i['earliest_exit'] - i['timestamp'] for i in a], timedelta())

    return sum_diff

但是,当我尝试在以下注释查询中使用子查询的结果时,它将失败:

b = EnterEvent.objects.filter(closed=True,
                              participant=player.participant,
                              page_name=page_name).annotate(
    earliest_exit=Subquery(earliest.values('timestamp')[:1]),
).annotate(timespent=ExpressionWrapper(F('earliest_exit') - F('timestamp'), output_field=DurationField()))

错误日志:

Exception Type: TypeError
Exception Value: can only concatenate tuple (not "list") to tuple
Exception Location: /Users/chapkovski/otree2/lib/python3.6/site-packages/django/db/backends/sqlite3/operations.py in subtract_temporals, line 280

我在做什么错了?

UPDATE :::

当Django尝试减去两个日期(其中一个由Subquery产生)时,会导致错误。 这是由于来自“普通”字段的参数是一个元组,而Subquery而是返回了一个列表,因此,当Django(在subtract_temporals函数中)试图对这两个值求和时,会导致错误:

 internal_type   'DateTimeField'
 lhs_params  ()
 rhs_params  []

2 个答案:

答案 0 :(得分:1)

本月,我两次遇到此问题,最终遇到了对我有用的黑客。我想将计算包含在QuerySet中,这样我就可以从数据库排序中受益,并且当用户希望按工期字段进行排序时,不必编写自定义排序。

所以我发现的解决方案是通过Subquery对两个列进行注释,因此最终结果将是两个列在内部都是一个列表,因此Django将成功正确地串联/计算持续时间,而不会产生ValueError异常。

以下是使用您的代码的示例:

from django.db.models import Subquery, OuterRef, F, ExpressionWrapper, DurationField

earliest = ExitEvent.objects.filter(enter_event=OuterRef('pk')).order_by('timestamp')
enter_timestamp = EnterEvent.objects.filter(pk=OuterRef('pk')).annotate(enter_timestamp=F('timestamp'))

EnterEvent.objects.filter(closed=True, participant=player.participant, page_name=page_name) \
    .annotate(earliest_exit=Subquery(earliest.values('timestamp')[:1])) \
    .annotate(enter_timestamp=Subquery(enter_timestamp.values('enter_timestamp')[:1])) \
    .annotate(timespent=ExpressionWrapper(F('earliest_exit') - F('enter_timestamp'), output_field=DurationField()))

答案 1 :(得分:1)

做到了。它正在工作。

在子查询中

使output_field = DurationField()代替DateTimeField()。并在减去两个字段时添加带有output_field = DurationField()的ExpressionWrapper。

ModelA.objects.filter()\
.annotate(jct=Subquery(ModelB.objects.filter(model_a_id=OuterRef('pk')).values_list('created_at',flat=True)[:1], output_field=DurationField()))\
.annotate(ans=ExpressionWrapper(F('jct') - F('created_at'), output_field=DurationField()))