You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django使用django-filter时出现'When' object is not iterable错误

问题:使用django-filter展示员工工时触发'When' object is not iterable错误

模型定义(models.py)

...
class Attendance(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="Attendances")
    day = models.DateField()
    start_time = models.TimeField()
    end_time = models.TimeField()
    is_confirmed = models.BooleanField()
    task = models.ForeignKey(Tasks, on_delete=models.CASCADE)
    
class Task(models.Model):
    title = models.CharField(max_length=255)
    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="Tasks")
    assignee = models.ForeignKey(User, on_delete=models.CASCADE)
...

过滤器定义(filter.py)

class WorkhourFilter(django_filters.FilterSet):
    user = django_filters.ModelChoiceFilter(queryset=User.objects.filter(is_superuser=False),field_name="user")
    project = django_filters.ModelChoiceFilter(queryset=Project.objects.all(), field_name="task__project")
    year = django_filters.ChoiceFilter(choices=tuple((x, x) for x in settings.YEARS_LIST),method='year_search')
    month = django_filters.ChoiceFilter(choices=tuple((i, x) for i, x in enumerate(settings.MONTH_LIST)),method='month_search' )
    
    class Meta:
        model = Attendance
        fields = ['user']
        
    def __init__(self, *args, **kwargs):
         super(WorkhourFilter, self).__init__(*args, **kwargs)
         start_date = ...
         end_date = ...
         self.queryset = self.Meta.model.objects.filter(user=self.request.user, 
         day__gte=start_date, day__lte=end_date).values('task__project__title', 
         'task__project').annotate(
          raw_work_hour=Sum(F('end_time') - F('start_time')),
          confirmed_work_hour=Sum(Case(When(is_verified=1, 
          then=F('end_time') - 
          F('start_time'))))).order_by('task__project')

错误信息

运行时触发'When' object is not iterable错误,仅confirmed_work_hour字段存在问题,单独使用raw_work_hour时无异常。该查询在视图的get_queryset()中可正常执行,但应用过滤器时报错,错误栈如下:

Django Version: 4.2.5
Python Version: 3.9.13
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django_extensions',
 'crispy_forms',
 'crispy_bootstrap5',
 'django_tables2',
 'django_htmx',
 'django_filters',
 'ckeditor',
 'ckeditor_uploader',
 'guardian',
 'django_cascading_dropdown_widget',
 'dashboard.apps.DashboardsConfig',
 'auth.apps.AuthConfig']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django_htmx.middleware.HtmxMiddleware']


Traceback (most recent call last):
  File "D:\Python\DjangoTest\venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Python\DjangoTest\venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "D:\Python\DjangoTest\venv\lib\site-packages\django\views\generic\base.py", line 104, in view
    return self.dispatch(request, *args, **kwargs)
  File "D:\Python\DjangoTest\venv\lib\site-packages\django\contrib\auth\mixins.py", line 73, in dispatch
    return super().dispatch(request, *args, **kwargs)
  File "D:\Python\DjangoTest\venv\lib\site-packages\braces\views\_access.py", line 438, in dispatch
    return super(StaffuserRequiredMixin, self).dispatch(
  File "D:\Python\DjangoTest\venv\lib\site-packages\braces\views\_access.py", line 375, in dispatch
    return super(GroupRequiredMixin, self).dispatch(
  File "D:\Python\DjangoTest\venv\lib\site-packages\django\views\generic\base.py", line 143, in dispatch
    return handler(request, *args, **kwargs)
  File "D:\Python\DjangoTest\venv\lib\site-packages\django_filters\views.py", line 74, in get
    self.filterset = self.get_filterset(filterset_class)
  File "D:\Python\DjangoTest\venv\lib\site-packages\django_filters\views.py", line 38, in get_filterset
    return filterset_class(**kwargs)
  File "D:\Python\DjangoTest\dashboard\filters\workhour_project.py", line 71, in __init__
    confirmed_work_hour=Sum(Case(When(is_verified=1, then=F('end_time') - F('start_time')))),
  File "D:\Python\DjangoTest\venv\lib\site-packages\sqlparse\sql.py", line 160, in __init__
    [setattr(token, 'parent', self) for token in self.tokens]

Exception Type: TypeError at /report/workhour/by_project
Exception Value: 'When' object is not iterable

解决方案

问题根源

  1. Case表达式参数格式错误:Case()需要接收可迭代的When对象集合(如列表、元组),直接传入单个When对象会导致解析失败。
  2. 字段名不匹配:模型Attendance中的字段是is_confirmed,但过滤器中写的是is_verified=1,属于字段名错误。
  3. 时间差聚合问题:直接对TimeField的差值(timedelta类型)使用Sum,部分数据库或Django版本无法正确处理,需转为数值类型(如小时数)。

修正后的过滤器代码

from django.db.models import Case, When, Sum, F, ExpressionWrapper, FloatField
from django.db.models.functions import ExtractHour, ExtractMinute

class WorkhourFilter(django_filters.FilterSet):
    user = django_filters.ModelChoiceFilter(queryset=User.objects.filter(is_superuser=False), field_name="user")
    project = django_filters.ModelChoiceFilter(queryset=Project.objects.all(), field_name="task__project")
    year = django_filters.ChoiceFilter(choices=tuple((x, x) for x in settings.YEARS_LIST), method='year_search')
    month = django_filters.ChoiceFilter(choices=tuple((i, x) for i, x in enumerate(settings.MONTH_LIST)), method='month_search')
    
    class Meta:
        model = Attendance
        fields = ['user']
        
    def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         start_date = ...
         end_date = ...
         # 将时间差转换为浮点型小时数(精确到分钟)
         work_hour_expr = ExpressionWrapper(
             ExtractHour(F('end_time') - F('start_time')) + 
             ExtractMinute(F('end_time') - F('start_time')) / 60,
             output_field=FloatField()
         )
         self.queryset = self.Meta.model.objects.filter(
             user=self.request.user, 
             day__gte=start_date, 
             day__lte=end_date
         ).values('task__project__title', 'task__project').annotate(
             raw_work_hour=Sum(work_hour_expr),
             # 修正Case参数格式,匹配正确字段名
             confirmed_work_hour=Sum(Case(
                 When(is_confirmed=True, then=work_hour_expr),
                 default=0,  # 未确认工时计为0
                 output_field=FloatField()
             ))
         ).order_by('task__project')

关键修正点

  • 给Case()传入的When对象后添加逗号,使其成为可迭代的元组(或用方括号包裹为列表)。
  • 将is_verified=1改为is_confirmed=True,与模型字段名保持一致。
  • 使用ExpressionWrapper结合ExtractHour和ExtractMinute,将时间差转换为可聚合的浮点型小时数。
  • 给Case添加default=0明确默认值,同时指定output_field保证数据类型一致。

内容的提问来源于stack exchange,提问作者Little Elite

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 11:17:07