问题背景
一个使用Django Rest Framework项目其中的一个查询页面,按照起始日期和终止日期范围查询。其中起始日期通过前端转递,终止日期通过默认值自动生成,默认值为今天。项目启动时验证功能正常。但是几天后发现查询不到近期的数据,当天和当天附近的数据无法查询到。
复现方法
为了复现和暴露问题方便,这里使用DateTimeField来代替DateField。因此可以看到时间信息。
下面只贴出和问题相关的代码:
serializers/rest_serializer.py
from rest_framework import serializers
import datetime
class RestSerializer(serializers.Serializer):
from_time = serializers.DateTimeField()
# 这里看出问题了吗?
to_time = serializers.DateTimeField(default=datetime.datetime.today())
该文件中,to_time字段指定了默认值,该默认值是动态的。开发者期待每次请求的时候默认值都为当前的时刻。
views/rest_view.py
from rest_framework.decorators import api_view
from demo_app.serializers.rest_serializer import RestSerializer
from django.http import HttpResponse
import json
import datetime
@api_view(['GET'])
def rest_view(request):
from_time = datetime.datetime.strptime(request.GET['from_time'], '%Y%m%dT%H:%M:%S')
serializer = RestSerializer({'from_time':from_time})
print(serializer.data)
return HttpResponse(json.dumps(serializer.data))
为了演示方便将serializer接收的值返回给浏览器。
启动Django测试服务器后,使用浏览器访问:http://127.0.0.1:8000/demo?from_time=20201010T20:00:00,看到页面返回如下:
{"from_time": "2020-10-10T20:00:00Z", "to_time": "2025-11-06T02:49:50.646458Z"}
反复刷新浏览器,发现to_time的值不会发生变化。我们期待的动态默认值完全没有起作用。背景中的问题已经复现。
问题原因
我们可以尝试重启Django测试服务器,再刷新浏览器,发现to_time字段会更新。但之后再反复刷新,字段值都不会更新。可以得出结论:按照当前的写法,所谓的动态默认值只会在服务启动的时候指定,之后就类似于静态默认值,再也不会得到更新。和问题背景中提到的现象完全吻合。
解决方式
经查阅相关资料,发现default还可以接受函数类型。按照这个思路,修改serializers/rest_serializer.py为:
from rest_framework import serializers
import datetime
class RestSerializer(serializers.Serializer):
from_time = serializers.DateTimeField()
# 注意,这里相比复现方法一节的代码,删除了today后面的括号,即传入的是datetime.datetime.today方法本身
to_time = serializers.DateTimeField(default=datetime.datetime.today)
再次重启Django测试服务器,反复刷新浏览器,发现每次刷新,to_time的值都会更新。问题得到解决。
总结
DRF框架的默认值赋值仅在项目启动的时候执行。遇到动态默认值这种情况,如果我们使用函数调用,则启动时函数调用的返回值会被当做默认值传入,和使用静态默认值(直接写常量)没有事实上的区别。如果要使用真正的动态默认值,需要在指定默认值的地方传入函数本身。两种写法仅有一对括号的区别,但结果相差甚远,并且造成的问题较为隐晦难以排查。因此撰写本文作为警示。