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

DRF集成shortuuid时RetrieveUpdateDestroyAPIView返回404问题

问题背景
  • 重构社交网站项目用于复习Django与Django Rest Framework技术栈
  • 未使用Django默认自增主键,因uuid生成的ID长度过长,引入shortuuid库,为帖子、评论模型配置ShortUUIDField作为主键,实现隐藏帖子、评论实际总数量的目的
问题现象
  • 帖子模块全量CRUD功能运行正常,可排除shortuuid库本身的兼容性问题
  • 评论模块仅创建、单条查询功能正常,更新、删除功能无法使用,访问对应评论详情接口固定返回404状态码,响应内容为{"detail": "Not found."}
  • 排查时在perform_update方法起始位置添加的打印语句始终未触发,判断问题出在对象查找阶段;先后尝试直接配置类属性queryset=Comment.objects.all()、重写get_queryset方法返回匹配路由参数的评论实例两种方案,均未解决404问题
相关业务代码

models.py(模型层定义)

from shortuuid.django_fields import ShortUUIDField

... # posts likes etc


class Comment(models.Model):
    id = ShortUUIDField(primary_key=True, length=8, max_length=10)
    user    = models.ForeignKey(User, on_delete=models.CASCADE)
    post    = models.ForeignKey(Post, on_delete=models.CASCADE)
    body    = models.TextField(max_length=350)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    active  = models.BooleanField(default=True)

    class Meta:
        ordering = ['created']

    def __str__(self):
        return f'on {self.post} by {self.user}'

    objects = models.Manager()

serializers.py(序列化器定义)

class CommentSerializer(ModelSerializer):
    username = SerializerMethodField()

    def get_username(self, comment):
        return str(comment.user)

    class Meta:
        model = Comment
        fields = ['id', 'user', 'post', 'username', 'body', 'created', 'updated']
        read_only_fields = ['id', 'post', 'user', 'username']

urls.py(路由配置)

from django.urls import path
from .views import *


urlpatterns = [
    ...

    path('<str:pk>/comments/'       , Comments),
    path('<str:pk>/comments/create/', CreateComment),
    path('<str:pk>/comments/<str:cm>/', ModifyComment),
    # pk = post ID
    # cm = comment ID
]

views.py(视图层定义)

class ModifyComment(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = CommentSerializer
    permission_classes = [permissions.AllowAny]

    def get_queryset(self):
        post = Post.objects.get(pk=self.kwargs['pk'])
        comment = Comment.objects.get(post=post, pk=self.kwargs['cm'])
        return comment


    def perform_update(self, serializer):
        print(Post.objects.all())
        post = Post.objects.get(pk=self.kwargs['pk'])
        comment = Comment.objects.filter(pk=self.kwargs['cm'], post=post)
        if self.request.user != comment.user:
            raise ValidationError('you can\'t edit another user\'s post')
        if comment.exists():
            serializer.save(user=self.request.user, comment=comment)
        else:
            raise ValidationError('the comment doesnt exist lol')

    def delete(self, request, *args, **kwargs):
        comment = Comment.objects.filter(user=self.request.user, pk=self.kwargs['cm'])
        if comment.exists():
            return self.destroy(request, *args, **kwargs)
        else:
            raise ValidationError("you can\'t delete another user\'s post")
ModifyComment = ModifyComment.as_view()
问题根因
  1. DRF默认查找参数不匹配:RetrieveUpdateDestroyAPIView默认通过lookup_field = 'pk'匹配URL中名为pk的路径参数作为主键查找对象,但路由中评论ID的参数名为cm,框架无法获取正确的主键值,在对象查找阶段直接返回404,根本不会进入后续的perform_update逻辑,这也是添加的打印语句不触发的核心原因。
  2. get_queryset返回值类型错误:该方法要求必须返回QuerySet对象,原代码中使用Comment.objects.get(...)获取的是单个Comment模型实例,不是QuerySet,框架无法基于单个实例执行后续的过滤、查找操作。
  3. 业务逻辑存在语法错误:
    • perform_update中comment = Comment.objects.filter(...)返回的是QuerySet集合,不存在user属性,直接判断self.request.user != comment.user会抛出属性错误
    • 调用serializer.save()时传入comment=comment为无效参数,Comment模型无对应字段
    • delete方法中直接用filter结果判断权限,未关联当前所属帖子,存在越权风险
修复方案
  1. 给视图类添加lookup_url_kwarg = 'cm'配置,告诉DRF从URL路径的cm参数中获取评论主键值,替代默认的pk参数匹配规则
  2. 修正get_queryset方法,仅返回当前帖子下的所有评论QuerySet,由框架自动完成单条评论的查找
  3. 修正更新、删除逻辑中的权限校验,基于框架查找出的评论对象做判断,避免直接操作QuerySet引发的错误

修复后的views.py参考代码:

class ModifyComment(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = CommentSerializer
    permission_classes = [permissions.AllowAny]
    lookup_url_kwarg = 'cm'  # 匹配URL中评论ID的参数名

    def get_queryset(self):
        # 仅返回当前帖子下的评论,必须返回QuerySet
        post = Post.objects.get(pk=self.kwargs['pk'])
        return Comment.objects.filter(post=post, active=True)

    def perform_update(self, serializer):
        # self.get_object() 已经拿到当前请求对应的评论实例
        comment = self.get_object()
        if self.request.user != comment.user:
            raise ValidationError('you can\'t edit another user\'s comment')
        # 仅传入需要更新的合法字段
        serializer.save(user=self.request.user, post=comment.post)

    def delete(self, request, *args, **kwargs):
        comment = self.get_object()
        if request.user != comment.user:
            raise ValidationError("you can\'t delete another user\'s comment")
        return self.destroy(request, *args, **kwargs)
ModifyComment = ModifyComment.as_view()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 08:18:33