Appearance
上一章我们做好了评论功能,就有了作者和读者沟通的方式。
但有的时候读者和读者同样需要沟通,评论别人的评论,俗称多级评论。
本章将实现基础的多级评论功能。
准确的讲是两级评论。
模型
多级评论,也就是让评论模型和自身相关联,使其可以有一个父级。
修改评论模型,新增 parent
字段:
python
# comment/models.py
...
class Comment(models.Model):
...
parent = models.ForeignKey(
'self',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='children'
)
- 一个父评论可以有多个子评论,而一个子评论只能有一个父评论,因此用了一对多外键。
- 之前的一对多外键,第一个参数直接引用了对应的模型,但是由于语法规则限制,这里显然不能够自己引用自己,因此用了传递字符串
self
的方式,作用都是一样的。
进行迁移后,模型就改好了。
序列化器
在原有的评论序列化器上修改:
python
# comment/serializers.py
...
# 新增这个类
class CommentChildrenSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='comment-detail')
author = UserDescSerializer(read_only=True)
class Meta:
model = Comment
exclude = [
'parent',
'article'
]
# 修改这个类
class CommentSerializer(serializers.ModelSerializer):
# 这是已有代码
url = serializers.HyperlinkedIdentityField(view_name='comment-detail')
author = UserDescSerializer(read_only=True)
# 以下是新增代码
article = serializers.HyperlinkedRelatedField(view_name='article-detail', read_only=True)
article_id = serializers.IntegerField(write_only=True, allow_null=False, required=True)
parent = CommentChildrenSerializer(read_only=True)
parent_id = serializers.IntegerField(write_only=True, allow_null=True, required=False)
def update(self, instance, validated_data):
validated_data.pop('parent_id', None)
return super().update(instance, validated_data)
class Meta:
...
新增代码大致可以分为三块,让我们来拆解它们:
- 为了让文章引用更人性化,将
article
改为超链接字段用了HyperlinkedRelatedField
,它和之前用过的HyperlinkedIdentityField
差别很小,你可以简化理解为HyperlinkedRelatedField
用于对外键关系,而HyperlinkedIdentityField
用于对当前模型自身。(完整的解释看这里) parent
为父评论,用了嵌套序列化器CommentChildrenSerializer
。注意这个序列化器的Meta
用exclude
来定义不需要的字段。- 由于我们希望父评论只能在创建时被关联,后续不能更改(很合理),因此覆写
def update(...)
,使得在更新评论时忽略掉parent_id
参数。
这就完成了。接下来测试。
测试
新建一个文章主键为 2 、父评论主键为 17 的评论:
python
...> http -a Obama:admin123456 post http://127.0.0.1:8000/api/comment/ parent_id=17 article_id=2 content='comment to parent comment 17'
HTTP/1.1 201 Created
...
{
"article": "http://127.0.0.1:8000/api/article/2/",
"author": {
"date_joined": "2020-07-02T07:57:06.116144Z",
"id": 2,
"last_login": null,
"username": "Obama"
},
"content": "comment to parent comment 17",
"created": "2020-12-28T08:21:22.636431Z",
"id": 20,
"parent": {
"author": {
"date_joined": "2020-07-02T07:57:06.116144Z",
"id": 2,
"last_login": null,
"username": "Obama"
},
"content": "New comment by Obama",
"created": "2020-12-28T08:08:05.646643Z",
"id": 17,
"url": "http://127.0.0.1:8000/api/comment/17/"
},
"url": "http://127.0.0.1:8000/api/comment/20/"
}
注意这里由于将 article
改为了嵌套序列化器(只读),因此用 article_id
进行外键赋值。
如果我想在更新评论内容的同时修改父评论:
python
...> http -a Obama:admin123456 PUT http://127.0.0.1:8000/api/comment/21/ parent_id=99 article_id=2 content='comment to parent comment 10'
HTTP/1.1 200 OK
...
{
...
"content": "comment to parent comment 10",
"parent": {
...
"id": 17,
}
}
content
修改成功而 parent_id
无变化,和预想的逻辑表现一致。
其他的请求形式就不赘述了,读者可以自行尝试。