Django-Vue搭建个人博客:评论
5619 views, 2021/03/15 updated Go to Comments
评论是博客作者和读者进行沟通的重要方式,也是博客作者检视自身文章质量的手段。
虽然有很多方式可以将评论功能托管给第三方(我也推荐这么做),不过本着学习的目的,接下来就试着自己实现简单的评论接口。
准备工作
评论功能比较独立,因此另起一个 comment
的 App:
(venv) > python manage.py startapp comment
注册到配置文件:
# drf_vue_blog/settings.py ... INSTALLED_APPS = [ ... 'comment', ]
接下来就是模型:
# comment/models.py from django.db import models from django.utils import timezone from article.models import Article from django.contrib.auth.models import User class Comment(models.Model): author = models.ForeignKey( User, on_delete=models.CASCADE, related_name='comments' ) article = models.ForeignKey( Article, on_delete=models.CASCADE, related_name='comments' ) content = models.TextField() created = models.DateTimeField(default=timezone.now) class Meta: ordering = ['-created'] def __str__(self): return self.content[:20]
模型包含一对多的作者外键、一对多的文章外键、评论实际内容、评论时间这4个字段。
执行 makemigrations
和 migrate
,准备工作就完成了。
视图和序列化
视图集和之前章节的差不多:
# comment/views.py from rest_framework import viewsets from comment.models import Comment from comment.serializers import CommentSerializer from comment.permissions import IsOwnerOrReadOnly class CommentViewSet(viewsets.ModelViewSet): queryset = Comment.objects.all() serializer_class = CommentSerializer permission_classes = [IsOwnerOrReadOnly] def perform_create(self, serializer): serializer.save(author=self.request.user)
接下来写评论的权限。
评论对用户身份的要求比文章的更松弛,非安全请求只需要是本人操作就可以了。
因此自定义一个所有人都可查看、仅本人可更改的权限:
# comment/permissions.py from rest_framework.permissions import BasePermission, SAFE_METHODS class IsOwnerOrReadOnly(BasePermission): message = 'You must be the owner to update.' def has_permission(self, request, view): if request.method in SAFE_METHODS: return True return request.user.is_authenticated def has_object_permission(self, request, view, obj): if request.method in SAFE_METHODS: return True return obj.author == request.user
进行非安全请求时,由于需要验证当前评论的作者和当前登录的用户是否为同一个人,这里用到了 def has_object_permission(...)
这个钩子方法,方法参数中的 obj
即为评论模型的实例。
看起来只需要实现这个 def has_object_permission(...)
就可以了,但还有一点点小问题:此方法是晚于视图集中的 def perform_create(author=self.request.user)
执行的。如果用户未登录时新建评论,由于用户不存在,接口会抛出 500 错误。
本着即使出错也要做出正确错误提示的原则,增加了 def has_permission(...)
方法。此方法早于 def perform_create(...)
执行,因此能够对用户登录状态做一个预先检查。
功能这样就实现了,但是重复的代码又出现了,让我们来消灭它。
删掉旧代码,把这个权限类修改为下面这样:
# comment/permissions.py # ... class IsOwnerOrReadOnly(BasePermission): message = 'You must be the owner to update.' def safe_methods_or_owner(self, request, func): if request.method in SAFE_METHODS: return True return func() def has_permission(self, request, view): return self.safe_methods_or_owner( request, lambda: request.user.is_authenticated ) def has_object_permission(self, request, view, obj): return self.safe_methods_or_owner( request, lambda: obj.author == request.user )
用匿名函数将有函数体(闭包)作为参数,传递到 def safe_methods_or_owner(...)
方法里执行,效果和之前是完全一样的。
接下来的东西就都轻车熟路了。
将视图集注册到路由:
# drf_vue_blog/urls.py ... # 这里直接导入 views 会冲突 from comment.views import CommentViewSet router.register(r'comment', CommentViewSet)
将评论的序列化器写了:
# comment/serializers.py from rest_framework import serializers from comment.models import Comment from user_info.serializers import UserDescSerializer class CommentSerializer(serializers.ModelSerializer): url = serializers.HyperlinkedIdentityField(view_name='comment-detail') author = UserDescSerializer(read_only=True) class Meta: model = Comment fields = '__all__' extra_kwargs = {'created': {'read_only': True}}
跟之前一样, url
超链接字段让接口的跳转更方便,author
嵌套序列化器让显示的内容更丰富。
最后让评论通过文章接口显示出来:
# article/serializers.py ... from comment.serializers import CommentSerializer class ArticleDetailSerializer(...): id = serializers.IntegerField(read_only=True) comments = CommentSerializer(many=True, read_only=True) ...
这就完成了,代码量很少就完成了新功能。
测试
发几个请求测试接口逻辑是否正确。
未登录用户新建评论:
> http POST http://127.0.0.1:8000/api/comment/ article=1 content='New comment by Obama' HTTP/1.1 403 Forbidden ... { "detail": "Authentication credentials were not provided." }
用之前注册好的用户 Obama
新建评论:
PS C:\Users\Dusai> http -a Obama:admin123456 POST http://127.0.0.1:8000/api/comment/ article=1 content='New comment by Obama' HTTP/1.1 201 Created ... { "article": 1, "author": { ... }, "content": "New comment by Obama", "created": "2020-12-28T05:57:55.092150Z", "id": 7, "url": "http://127.0.0.1:8000/api/comment/7/" }
用非本人用户 dusai
更新评论:
...> http -a dusai:admin123456 POST http://127.0.0.1:8000/api/comment/7/ article=1 content='Updated by API' HTTP/1.1 405 Method Not Allowed ... { "detail": "Method \"POST\" not allowed." }
用 Obama
删除评论:
...> http -a Obama:admin123456 DELETE http://127.0.0.1:8000/api/comment/7/ HTTP/1.1 204 No Content Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS Content-Length: 0 Date: Mon, 28 Dec 2020 05:59:46 GMT Referrer-Policy: same-origin Server: WSGIServer/0.2 CPython/3.8.2 Vary: Accept, Cookie X-Content-Type-Options: nosniff X-Frame-Options: DENY
非本人无法对资源进行更改,很好的符合了预期逻辑。