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

Django Rest Framework序列化器数据异常及PostgreSQL搜索失效问题

问题与解决方案

问题详情

初始问题

实现了基于正文内容搜索帖子的视图,断点测试时print(serializer)能看到匹配的帖子QuerySet,但print(serializer.data)返回空字典,前端显示body:null,使用PostgreSQL数据库。

相关代码片段:

  • 初始视图(POST请求)
class SearchPosts(APIView):
    permission_classes = [IsAuthenticated]    

    def post(self, request, *args, **kwargs):
        term = request.data.get("term")
        posts = Post.objects.filter(body__search=term)
        serializer = SearchSerializer(posts)
        breakpoint()
        return Response(serializer.data, status=HTTP_200_OK)
  • 序列化器
class SearchSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['body']
  • Post模型
class Post(models.Model):
    body = models.TextField("content", blank=True, null=True, max_length=5000)
    slug = AutoSlugField(populate_from=["category", "created_at"])
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
    published = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = "post"
        verbose_name_plural = "posts"
        db_table = "posts"
        ordering = ["created_at"]
        get_latest_by = "created_at"

    def __str__(self):
        return self.body[0:30]

    def get_absolute_url(self):
        return self.slug

更新后的问题

请求改为GET方式,URL为/api/v1/posts/search/?term=looking,request.data返回None,改用request.GET.get("term")能获取到正确的搜索词,但body__search=term过滤返回空QuerySet;换成ListAPIView后依然返回空数组,移除过滤器则能正常返回所有帖子。


解决方案

1. 修复serializer.data为空的问题

序列化QuerySet(多个对象)时必须添加many=True参数,否则序列化器无法正确处理集合类型数据:

serializer = SearchSerializer(posts, many=True)

2. GET请求获取参数的规范方式

DRF推荐使用request.query_params获取URL参数,比直接用request.GET兼容性更好:

term = self.request.query_params.get("term")

3. 解决body__search失效的问题

PostgreSQL的__search查询依赖全文索引,需满足以下条件:

  • 字段需为TextField或CharField,且配置全文索引
  • 数据库中已创建对应索引

方案一:使用SearchVector直接搜索(无需提前建索引)

适合小型数据集,直接通过注解生成搜索向量:

from django.contrib.postgres.search import SearchVector

class SearchPosts(generics.ListAPIView):
    permission_classes = [IsAuthenticated]    
    serializer_class = SearchSerializer
    
    def get_queryset(self):
        term = self.request.query_params.get("term")
        if not term:
            return Post.objects.none()
        return Post.objects.annotate(
            search=SearchVector('body'),
        ).filter(search=term)

方案二:创建全文索引优化性能(适合大数据集)

  1. 修改Post模型,添加索引配置:
from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import SearchVectorField

class Post(models.Model):
    body = models.TextField("content", blank=True, null=True, max_length=5000, db_index=True)
    # 新增搜索向量字段,用于存储预计算的全文索引
    search_vector = SearchVectorField(null=True)
    
    class Meta:
        indexes = [
            GinIndex(fields=['search_vector']),
        ]
  1. 生成并执行迁移:
python manage.py makemigrations
python manage.py migrate
  1. 视图中使用预生成的索引字段搜索:
from django.contrib.postgres.search import SearchVector, SearchQuery, SearchRank

class SearchPosts(generics.ListAPIView):
    permission_classes = [IsAuthenticated]    
    serializer_class = SearchSerializer
    
    def get_queryset(self):
        term = self.request.query_params.get("term")
        if not term:
            return Post.objects.none()
        query = SearchQuery(term)
        return Post.objects.annotate(
            rank=SearchRank('search_vector', query),
        ).filter(search_vector=query).order_by('-rank')

4. 最终可用视图示例

结合以上修复,完整的GET搜索视图如下:

from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from django.contrib.postgres.search import SearchVector
from .models import Post
from .serializers import SearchSerializer

class SearchPosts(generics.ListAPIView):
    permission_classes = [IsAuthenticated]    
    serializer_class = SearchSerializer
    
    def get_queryset(self):
        term = self.request.query_params.get("term")
        if not term:
            return Post.objects.none()
        return Post.objects.annotate(
            search=SearchVector('body'),
        ).filter(search=term)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 23:03:07