Django返回JsonResponse时如何让QuerySet包含主键PK
问题回顾
你定义了如下的Comment模型:
class Comment(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, to_field='username') video = models.ForeignKey(Video, on_delete=models.CASCADE) description = models.TextField(default="none") replyto = models.ForeignKey('self', blank=True, null=True, on_delete=models.CASCADE) uploaded = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) indent = models.IntegerField(default=0)
原本通过这个QuerySet查询评论并序列化返回:
Comment.objects.filter(video_id=Video.objects.get(identifier=request.GET['identifier'])).order_by('-uploaded')[:end]
但返回的结果里没有包含评论的主键(pk),于是你尝试用value_list来指定字段,却触发了错误:AttributeError: 'Manager' object has no attribute 'value_list'
你的序列化代码如下:
def obtain_comments(request, *args, **kwargs): begin = int(request.GET['begin']) end = int(request.GET['end']) n_comments = end - begin all_split = Comment.objects.filter(video_id=Video.objects.get(identifier=request.GET['identifier'])).order_by('-uploaded')[:end] data = { 'comments': serializers.serialize('json',all_split), } return JsonResponse(data)
错误原因
这个错误的核心是拼写错误:Django QuerySet的方法是values_list(注意是复数values),而你写成了value_list(单数)。不过就算你改对了拼写,这里还有个问题:values_list返回的是元组的序列,而不是模型实例,后续用serializers.serialize处理会报错,因为序列化器需要的是Django模型实例对象。
解决方案
这里有两种符合你需求的思路,根据你的场景选择:
思路1:直接获取包含pk的字典序列(无需模型序列化)
如果你不需要保留模型实例的其他特性,只想拿到包含pk的纯数据字典,可以用values()方法(同样是复数),它会返回字典组成的QuerySet,每个字典包含你指定的字段,包括pk。注意要把方法放在filter、order_by等查询操作之后:
all_split = Comment.objects.filter( video_id=Video.objects.get(identifier=request.GET['identifier']) ).order_by('-uploaded')[:end].values( 'pk', 'user', 'video', 'description', 'replyto', 'uploaded', 'updated', 'indent' )
然后序列化的时候直接转成列表即可,不需要用Django的serializers.serialize:
data = { 'comments': list(all_split), } return JsonResponse(data)
这样返回的每个评论对象里就会包含pk字段了。
思路2:保留模型序列化,把pk加入返回字段
如果你仍然想用Django的serializers.serialize(比如需要保留模型相关的元信息),那默认的序列化结果其实已经包含pk了,只是它不在fields里,而是作为单独的键存在。你可以在后端把pk合并到fields中,再返回给前端:
import json def obtain_comments(request, *args, **kwargs): begin = int(request.GET['begin']) end = int(request.GET['end']) n_comments = end - begin all_split = Comment.objects.filter(video_id=Video.objects.get(identifier=request.GET['identifier'])).order_by('-uploaded')[:end] # 序列化并处理pk字段 serialized_data = json.loads(serializers.serialize('json', all_split)) processed_comments = [] for item in serialized_data: # 把pk加入到fields字典中 item['fields']['pk'] = item['pk'] processed_comments.append(item['fields']) data = { 'comments': processed_comments, } return JsonResponse(data)
这样处理后,返回的每个评论对象就和你之前的格式一致,同时多了pk字段。
额外小提示
另外,你代码里Video.objects.get(identifier=request.GET['identifier'])如果找不到对应的视频会抛出DoesNotExist异常,建议加上异常处理,避免请求直接崩溃~
内容的提问来源于stack exchange,提问作者BRHSM

