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

Django REST Framework中KeyError问题排查及预期输出实现

Django REST Framework 日历事件接口KeyError排查与分组实现

问题背景

开发事件日历列表接口时出现KeyError错误,需实现按日期分组的事件列表输出,相关代码及错误如下:

原代码文件

Views.py

class EventCalenderEventListView(ListAPIView):
    permission_classes=[AllowAny,]
    serializer_class = serializer.EventCalenderSerializer
    def get_queryset(self, *args, **kwargs):
        data = json.loads(self.request.GET.get('filter'))
        category = data.get('category')
        query_list = Event.objects.all().values('start_date').distinct()
        date_list = []
        for i in query_list:
            for key, value in i.items():
                date_list.append(datetime.datetime.strftime(value,'%Y-%m-%d'))
        datas = []
        result = {}
        for dates in date_list:
            result[dates]= []
            query = Event.objects.filter(start_date=dates).order_by('start_date')
            for i in query:
                result[dates].append(i)
            datas.append(result)
            result = {}
        return datas

Models.py

class Event(BaseFields):
    name = models.CharField(max_length=250)
    category = models.ForeignKey(EventCategory,on_delete=models.CASCADE,null=True,blank=True)
    other_event_category = models.CharField(max_length=250,null=True,blank=True)
    meeting_link = models.CharField(max_length=250,null=True,blank=True)
    description = models.TextField(null=True,blank=True)
    start_date = models.DateField(null=True,blank=True)
    end_date = models.DateField(null=True,blank=True) 

Serializer.py

class EventCalenderSerializer(ModelSerializer):
    category = SerializerMethodField()
    status = SerializerMethodField()
    class Meta:
        model = Event
        fields = fields=('id','name','category','start_date','end_date','status')
    def get_category(self, obj):
        category = {}
        if obj.category:
            category['id'] = obj.category.id
            category['name'] = obj.category.name
            return category
        else:
            category = ""
            return category
    def get_status(self,obj):
        current_date =  datetime.strftime(datetime.today(),'%Y-%m-%d')
        start_date = datetime.strftime(obj.start_date,'%Y-%m-%d')
        if obj.end_date is not None:
            end_date = datetime.strftime(obj.end_date,'%Y-%m-%d')
            if start_date == end_date and start_date < current_date:
                return "completed"
            elif start_date < current_date and end_date < current_date:
                return "completed"
            elif start_date < current_date and end_date > current_date:
                return "progress"
            elif start_date == current_date:
                return "progress"
            else:
                return "upcoming"
        else:
            if start_date < current_date:
                return "completed"
            elif start_date == current_date:
                return "progress"
            else:
                return "upcomng"

错误说明

KeyError触发原因:视图get_queryset返回字典列表而非DRF要求的QuerySet对象,序列化器尝试按模型字段解析非模型数据时,无法匹配对应键值。

预期输出

[
  {
    "2023-06-01": [
      {
        "name": "event 1",
        "start_date": "2023-06-01",
        "end_date": "2023-06-07"
      }
    ]
  },
  {
    "2023-06-02": [
      {
        "name": "event 2",
        "start_date": "2023-06-02",
        "end_date": "2023-06-07"
      },
      {
        "name": "event 3",
        "start_date": "2023-06-02",
        "end_date": "2023-06-07"
      }
    ]
  }
]

错误原因分析

  1. 视图返回数据类型错误:ListAPIView要求get_queryset返回QuerySet,直接返回字典列表会导致序列化逻辑混乱,触发KeyError。
  2. 日期匹配类型不兼容:用字符串格式的日期筛选DateField类型的start_date,可能导致查询失败,后续字典操作出错。
  3. 冗余查询性能低下:循环中多次执行数据库查询,效率极低且易引发数据不一致。
  4. 序列化器拼写错误:get_status方法中返回的"upcomng"是拼写错误,应为"upcoming"。

修复方案与代码

核心调整

  • 放弃ListAPIView的默认序列化流程,重写get方法直接构造自定义响应。
  • 一次性查询所有事件,在内存中按日期分组,减少数据库查询次数。
  • 统一日期类型转换,避免字符串与DateField的匹配错误。
  • 修正序列化器拼写错误。

修正后代码

Views.py

from django.http import JsonResponse
from rest_framework.views import APIView
from rest_framework.permissions import AllowAny
import json
from datetime import datetime
from .models import Event
from .serializer import EventCalenderSerializer

class EventCalenderEventListView(APIView):
    permission_classes = [AllowAny,]

    def get(self, request, *args, **kwargs):
        # 处理筛选参数
        filter_data = request.GET.get('filter')
        category = None
        if filter_data:
            try:
                data = json.loads(filter_data)
                category = data.get('category')
            except json.JSONDecodeError:
                pass
        
        # 查询所有事件,支持分类筛选
        queryset = Event.objects.all().order_by('start_date')
        if category:
            queryset = queryset.filter(category__id=category)
        
        # 按日期分组
        date_groups = {}
        for event in queryset:
            if not event.start_date:
                continue  # 跳过无开始日期的事件
            date_str = event.start_date.strftime('%Y-%m-%d')
            if date_str not in date_groups:
                date_groups[date_str] = []
            # 序列化单个事件
            serializer = EventCalenderSerializer(event)
            date_groups[date_str].append(serializer.data)
        
        # 转换为预期的列表格式
        result = [{date: events} for date, events in date_groups.items()]
        return JsonResponse(result, safe=False)

Serializer.py

from rest_framework import serializers
from .models import Event
from datetime import datetime

class EventCalenderSerializer(serializers.ModelSerializer):
    category = serializers.SerializerMethodField()
    status = serializers.SerializerMethodField()

    class Meta:
        model = Event
        fields = ('id','name','category','start_date','end_date','status')

    def get_category(self, obj):
        if obj.category:
            return {'id': obj.category.id, 'name': obj.category.name}
        return ""

    def get_status(self, obj):
        current_date = datetime.today().date()
        start_date = obj.start_date
        
        if not start_date:
            return "unknown"
        
        if obj.end_date:
            end_date = obj.end_date
            if start_date == end_date and start_date < current_date:
                return "completed"
            elif start_date < current_date and end_date < current_date:
                return "completed"
            elif start_date < current_date and end_date > current_date:
                return "progress"
            elif start_date == current_date:
                return "progress"
            else:
                return "upcoming"
        else:
            if start_date < current_date:
                return "completed"
            elif start_date == current_date:
                return "progress"
            else:
                return "upcoming"  # 修正拼写错误

验证结果

修复后接口将返回符合预期的按日期分组的事件列表,且无KeyError错误。

内容的提问来源于stack exchange,提问作者Jerin Raju

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 18:33:17