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
解决方案
问题根源
- Case表达式参数格式错误:
Case()需要接收可迭代的When对象集合(如列表、元组),直接传入单个When对象会导致解析失败。 - 字段名不匹配:模型
Attendance中的字段是is_confirmed,但过滤器中写的是is_verified=1,属于字段名错误。 - 时间差聚合问题:直接对
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
相关产品推荐
相关产品推荐

