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

