Skip to content

Commit d62b5ce

Browse files
committedJun 8, 2021
Merge branch 'master' of github.com:stacklens/django-vue-tutorial
u
2 parents ca76cee + 365a0a7 commit d62b5ce

32 files changed

+1390
-1403
lines changed
 

‎md/10-前言.md

-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@
130130

131131
- **署名** — 您必须给出**适当的署名**,提供指向本许可协议的链接,同时标明是否(对原始作品)作了修改。您可以用任何合理的方式来署名,但是不得以任何方式暗示许可人为您或您的使用背书。
132132
- **非商业性使用** — 您不得将本作品用于**商业目的**
133-
134133
- **没有附加限制** — 您不得适用法律术语或者技术措施从而限制其他人做许可协议允许的事情。
135134

136135
> 适当的署名:您必须提供创作者和署名者的姓名或名称、版权标识、许可协议标识、免责标识和作品链接。

‎md/100-过滤文章.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
有些时候用户仅仅需要某个特定范围的文章(比如搜索功能),所以后端需要把返回的数据进行过滤
1+
有些时候用户需要某个特定范围的文章(比如搜索功能),这时候后端需要把返回的数据进行过滤
22

33
最简单的过滤方法就是修改视图集中的 `queryset` 属性了:
44

@@ -14,7 +14,7 @@ class ArticleViewSet(viewsets.ModelViewSet):
1414

1515
假设有如下带有参数的 GET 请求:
1616

17-
```python
17+
```
1818
http://127.0.0.1:8000/api/article/?username=Obama
1919
```
2020

@@ -35,13 +35,13 @@ class ArticleViewSet(viewsets.ModelViewSet):
3535
return queryset
3636
```
3737

38-
这样就实现了过滤。但是如此常用的功能,必然已经有现成的轮子了。在定制化程度不高的博客项目中,使用轮子可以更快更好的完成任务,所以接下来就来看看通用的过滤功能。
38+
这样就实现了过滤。但是如此常用的功能,必然已经有现成的轮子了。在博客项目中使用轮子可以更快更好的完成任务,所以接下来就来看看通用的过滤功能。
3939

40-
> 注意上面那个请求尾部的斜杠。虽然在浏览器中这个斜杠是可选的,但是在命令行中发送请求是必须要携带的(DRF 3.11.0)。想要斜杠可选需要做一些定制工作,这里就不展开讲了。
40+
> 注意上面那个请求尾部的斜杠。虽然在浏览器中这个斜杠是可选的,但是在命令行中发送请求是必须要携带的(DRF 3.11.0)。
4141
4242
## 通用过滤
4343

44-
还记得刚安装 DRF 时顺便安装的 `django-filter` 吗,这就是用于过滤的轮子,现在排上用场了
44+
还记得刚安装 DRF 时顺便安装的 `django-filter` 吗,这就是用于过滤的轮子,现在派上用场了
4545

4646
要将它作为默认的过滤引擎后端,写到配置文件中:
4747

‎md/110-文章分类.md

+16-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
博客文章通常需要分类,方便用户快速识别文章的类型,或者进行过滤操作。
1+
博客文章通常需要分类,方便用户快速识别文章的类型,或者进行某种关联操作。
2+
3+
本章就来实现对文章的分类。
24

35
## 增加分类模型
46

@@ -34,11 +36,11 @@ class Article(models.Model):
3436

3537
```
3638

37-
比较简单的分类模型,大体上就 `title` 字段会用到。
39+
字段很简单,大体上就 `title` 字段会用到。
3840

3941
别忘了数据迁移。
4042

41-
> 教程为了简单起见就把分类的 model 放到 article 目录中了。实际项目应根据情况考虑是否需要另起一个单独的分类 app。
43+
> 教程把分类的 model 放到 article app中了。实际项目应根据情况考虑是否需要另起一个单独的分类 app。
4244
4345
## 视图与路由
4446

@@ -126,26 +128,26 @@ class ArticleSerializer(serializers.HyperlinkedModelSerializer):
126128

127129
先看 `CategorySerializer`
128130

129-
- `HyperlinkedIdentityField` 前面章节有讲过,作用是将路由间的表示转换为超链接。`view_name` 参数是路由的别名,你必须显示指定。 `category-detail` 是自动注册路由时,`Router` 默认帮你设置的详情页面的名称,类似的还有 `category-list` 等,更多规则参考[文档](https://www.django-rest-framework.org/api-guide/routers/#defaultrouter)
131+
- `HyperlinkedIdentityField` 前面章节有讲过,作用是将路由间的表示转换为超链接。`view_name` 参数是路由名,你必须显示指定。 `category-detail` 是自动注册路由时,`Router` 默认帮你设置的详情页面的名称,类似的还有 `category-list` 等,更多规则参考[文档](https://www.django-rest-framework.org/api-guide/routers/#defaultrouter)
130132
- 创建日期不需要后期修改,所以设置为 `read_only_fields`
131133

132-
然后再来看更麻烦一点的 `ArticleSerializer`
134+
再来看 `ArticleSerializer`
133135

134-
- 由于我们希望文章接口不仅仅只返回分类的 id 而已,所以需要显示指定 `category` ,将其变成一个嵌套数据,与之前的 `author` 类似。
135-
- DRF 框架原生没有实现**可写的嵌套数据**(因为其操作逻辑没有统一的标准),那我想**创建/更新**文章和分类的外键关系怎么办?一种方法是自己去实现序列化器的 `create()/update()` 方法;另一种就是 DRF 框架提供了修改外键的一个快捷方式,即显示指定 `category_id` 字段,则此字段会自动链接到 `category` 外键,以便你更新外键关系。
136+
- 由于我们希望文章接口不仅仅只返回分类的 id 而已,所以需要显式指定 `category` ,将其变成一个嵌套数据,与之前的 `author` 类似。
137+
- DRF 框架原生没有实现**可写的嵌套数据**(因为其操作逻辑没有统一的标准),那我想**创建/更新**文章和分类的外键关系怎么办?一种方法是自己去实现序列化器的 `create()/update()` 方法;另一种就是 DRF 框架提供的修改外键的快捷方式,即显式指定 `category_id` 字段,则此字段会自动链接到 `category` 外键,以便你更新外键关系。
136138
- 再看 `category_id` 内部。`write_only` 表示此字段仅需要可写;`allow_null` 表示允许将其设置为空;`required` 表示在**创建/更新**时可以不设置此字段。
137139

138140
经过以上设置,实际上序列化器已经可以正常工作了。但有个小问题是如果用户提交了一个不存在的分类外键,后端会返回外键数据不存在的 500 错误,不太友好。解决方法就是对数据预先进行**验证**
139141

140142
验证方式又有如下几种:
141143

142-
- 覆写序列化器的 `validate()` 方法。这是个全局的验证器,其接收的唯一参数是所有字段值的字典。当你需要同时对多个字段进行验证时,这是个很好的选择。
143-
- 另一种就是教程用的方法,即 `validate_{field_name}` 方法,它会只验证某个特定的字段,比如 `category_id`
144+
- 覆写序列化器的 `.validate(...)` 方法。这是个全局的验证器,其接收的唯一参数是所有字段值的字典。当你需要同时对多个字段进行验证时,这是个很好的选择。
145+
- 另一种就是教程用到的,即 `.validate_{field_name}(...)` 方法,它会只验证某个特定的字段,比如 `category_id`
144146

145147
`validate_category_id` 检查了两样东西:
146148

147149
- 数据库中是否包含了对应 id 值的数据。
148-
- 传入值是否为 None。这是为了能够将已有的外键置空而设置的
150+
- 传入值是否为 None。这是为了能够将已有的外键置空
149151

150152
如果没通过上述检查,后端就抛出一个 400 错误(代替之前的 500 错误),并返回错误产生的提示,这就更友好一些了。
151153

@@ -155,9 +157,9 @@ class ArticleSerializer(serializers.HyperlinkedModelSerializer):
155157

156158
打开命令行,首先创建分类:
157159

158-
> 你创建的数据 id 和博主的不一定相同,不要惊慌这是正常的,因为我在写文章时会反复进行测试,确保正确。再重复一次,如果你更喜欢图形化的界面,请下载 Postman
160+
> 你创建的数据 id 和博主的不一定相同,这是正常的,因为我在写教程时会反复测试,确保正确。
159161
160-
```
162+
```python
161163
C:\...> http -a dusai:admin123456 POST http://127.0.0.1:8000/api/category/ title=Django
162164
...
163165

@@ -214,7 +216,7 @@ C:\Users\Dusai>http -a dusai:admin123456 PATCH http://127.0.0.1:8000/api/article
214216
}
215217
```
216218

217-
这里细心一点的就会发现,我们在更新资源时用到了 `POST``PUT``PATCH` 三种请求方法,它们在 DRF 中的区别是啥
219+
这里细心一点的就会发现,在更新资源时用到了 `POST``PUT``PATCH` 三种请求方法,它们的区别是啥
218220

219221
- `POST` :创建新的资源。
220222
- `PUT` : 整体更新特定资源,默认情况下你需要完整给出所有必须的字段。
@@ -279,4 +281,4 @@ class CategoryViewSet(viewsets.ModelViewSet):
279281
return CategoryDetailSerializer
280282
```
281283

282-
除此之外没有新的魔法,轻松搞定
284+
除此之外没有新的魔法。

‎md/120-文章标签.md

+14-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
一篇文章通常还有多个标签,作为分类功能的补充
1+
一篇文章通常还有**标签**功能,作为分类的补充
22

33
## 模型和视图
44

@@ -31,7 +31,9 @@ class Article(models.Model):
3131
)
3232
```
3333

34-
一篇文章可以有多个标签,一个标签可以对应多个文章,因此是**多对多**关系。写完后记得迁移。
34+
一篇文章可以有多个标签,一个标签可以对应多个文章,因此是**多对多**关系。
35+
36+
> 写完后记得迁移。
3537
3638
接着把视图集也写好:
3739

@@ -62,7 +64,7 @@ router.register(r'tag', views.TagViewSet)
6264

6365
## 序列化器
6466

65-
接下来就是最重要的`TagSerializer`
67+
接下来就是最重要的 `TagSerializer`
6668

6769
```python
6870
# article/serializers.py
@@ -92,7 +94,7 @@ class ArticleSerializer(serializers.HyperlinkedModelSerializer):
9294
...
9395
```
9496

95-
我们已经知道,默认的嵌套序列化器显示外链的 id,需要改得更友好一些。但似乎又没必要改为超链接,因为标签就 `text` 字段有用。因此就用 `SlugRelatedField` 直接显示其 `text` 字段得内容就足够了
97+
通过前面章节已经知道,默认的嵌套序列化器只显示外链的 id,需要改得更友好一些。但似乎又没必要改为超链接或者字段嵌套,因为标签就 `text` 字段有用。因此就用 `SlugRelatedField` 直接显示其 `text` 字段的内容就足够了
9698

9799
让我们给已有的文章新增一个叫 `java` 的标签试试:
98100

@@ -107,13 +109,13 @@ PS C:\...> http -a dusai:admin123456 PATCH http://127.0.0.1:8000/api/article/26/
107109
}
108110
```
109111

110-
> 这里再强调一下,与发送请求有关的命令行均指 PowerShell。指令里 tags 那里面带那么多斜杠的写法都是 windows 的老毛病造成的。用 Postman 并不需要。
112+
> 指令里 tags 里面带那么多斜杠的写法都是 windows 的老毛病造成的。用 Postman 并不需要。
111113
112-
修改失败了,原因是 `java` 标签不存在。多对多关系,DRF 框架默认你必须先得有这个外键对象,才能指定其关系。虽然也合情合理,但我们更希望在创建、更新文章时,程序会**自动检查**数据库里是否存在当前标签。如果存在则链接它,如果不存在则创建一个并链接它
114+
修改失败了,原因是 `java` 标签不存在。多对多关系,DRF 默认你必须先得有这个外键对象,才能指定其关系。虽然也合情合理,但我们更希望在创建、更新文章时,程序会**自动检查**数据库里是否存在当前标签。如果存在则指向它,如果不存在则创建一个并指向它
113115

114-
要实现这个效果,你可以很快就会想到覆写 `validate_{field_name}()` 或者 `validate()` 还或者 `create()/update()` 方法。但是很遗憾,它们都是不行的。
116+
要实现这个效果,你可能想到覆写 `.validate_{field_name}()` 或者 `.validate()` 还或者 `.create()/.update()` 方法。但是很遗憾,它们都是不行的。
115117

116-
原因是 DRF 框架执行默认的字段有效性验证比上述的方法都早,程序还执行不到上述的方法,框架就已经抛出错误了。
118+
原因是 DRF 执行默认的字段有效性检查比上述的方法都早,程序还执行不到上述的方法,框架就已经抛出错误了。
117119

118120
正确的解法是覆写 `to_internal_value()` 方法:
119121

@@ -136,7 +138,7 @@ class ArticleSerializer(serializers.HyperlinkedModelSerializer):
136138
return super().to_internal_value(data)
137139
```
138140

139-
`to_internal_value()` 方法本身的主要作用是将请求中的原始数据转化为 Python 表示形式(期间还会对字段有效性做初步检查)。它的执行时间比默认验证器的字段检查更早,因此有机会在此方法中将需要的数据创建好,然后等待检查的降临。`isinstance()` 确定标签数据是列表,才会循环并创建新数据。
141+
`to_internal_value()` 方法原本作用是将请求中的原始 Json 数据转化为 Python 表示形式(期间还会对字段有效性做初步检查)。它的执行时间比默认验证器的字段检查更早,因此有机会在此方法中将需要的数据创建好,然后等待检查的降临。`isinstance()` 确定标签数据是列表,才会循环并创建新数据。
140142

141143
再重新请求试试:
142144

@@ -249,4 +251,6 @@ class ArticleSerializer(serializers.HyperlinkedModelSerializer):
249251
fields = '__all__'
250252
```
251253

252-
标签的增删改查,请读者自行测试吧。
254+
标签的增删改查,就请读者自行测试吧。
255+
256+
> 无论是通过文章接口还是标签自己的接口,创建新标签应该都是 OK 的。

‎md/130-Markdown正文.md

+14-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
博客文章需要排版,否则难以凸显标题、正文、注释等内容之间的区别。作为博客写手来说,比较流行且好用的排版则是采用 Markdown 语法。
1+
博客文章需要排版,否则难以凸显标题、正文、注释等内容之间的区别。作为博客写手来说,比较流行且好用的排版是采用 Markdown 语法。
22

3-
> 如果你没听过什么是 Markdown [这里](https://www.dusaiphoto.com/article/20/) 有一些简单介绍
3+
严格来说, Markdown 是一种排版标注规则。它将两个星号包裹的文字标注为重要文本(通常也就是粗体字),比如原始文本中的 `**Money**` ,在 Markdown 语法中应该被”渲染“为粗体,也就是 **Money** 。类似的还有斜体、代码块、表格、公式等注释,就请读者自行了解了
44

5-
渲染 Markdown 可以在前端也可以在后端。本文将使用后端渲染 Markdown 语法的形式,以便你理解 DRF 框架的知识。
5+
> [关于 Markdown](https://www.dusaiphoto.com/article/20/) 的简单介绍。
6+
7+
”渲染“ Markdown 也就是把原始文本中的注释转化为前端中真正被用户看到的 HTML 排版文字。渲染过程可以在前端也可以在后端,本文将使用后端渲染,以便你理解 DRF 的相关知识。
68

79
## 模型和视图
810

9-
为了将博文的正文部分渲染为 html 标签,首先给文章模型添加一个 `get_md()` 方法:
11+
为了将博文的 Markdown 正文渲染为 html 标签,首先给文章模型添加一个 `get_md()` 方法:
1012

1113
```python
1214
# article/models.py
@@ -31,9 +33,9 @@ class Article(models.Model):
3133
return md_body, md.toc
3234
```
3335

34-
方法返回了元组,包含了渲染为 html 的正文和目录
36+
方法返回了包含了两个元素的元组,分别为已渲染为 html **正文****目录**
3537

36-
这些渲染后的数据,在文章详情接口是需要的,但是在列表接口却是毫无意义的,因此又要用到视图集根据请求方式动态获取序列化器的技术了:
38+
这些渲染后的数据,在文章**详情接口**是需要的,但是在**列表接口**却没太有必要,因此又要用到视图集根据请求方式动态获取序列化器的技术了:
3739

3840
```python
3941
# article/views.py
@@ -57,7 +59,7 @@ class ArticleViewSet(viewsets.ModelViewSet):
5759

5860
## 序列化器
5961

60-
因为**文章列表接口****详情接口**只有一点点返回字段的区别,其实大部分功能还是一样的。那么常年被面向对象编程熏陶的你,“把他抽象成父类!” 应该可以脱口而出:
62+
因为**文章列表接口****详情接口**只有一点点返回字段的区别,其实大部分功能还是一样的。那么被面向对象编程熏陶的你,“把他抽象成父类!” 应该可以脱口而出:
6163

6264
```python
6365
# article/serializers.py
@@ -89,7 +91,7 @@ class ArticleSerializer(ArticleBaseSerializer):
8991

9092
与 Django 表单类似,你可以继承扩展和重用序列化器。就像上面的代码一样,在父类上声明一组通用的字段或方法,然后在许多序列化程序中使用它们。
9193

92-
但是 `Meta` 内部类比较特殊,它不会隐式从父类继承。虽然有办法让它隐式继承,但这是不被推荐的,你应该显式声明它,以使得其行为更清晰
94+
但是内部类 `class Meta` 比较特殊,它不会隐式从父类继承。虽然有办法让它隐式继承,但这是不被推荐的,你应该显式声明它,以使得序列化器的行为更清晰
9395

9496
另外,如果你觉得在列表接口连 `body` 字段也不需要显示的话,你可以传入 `extra_kwargs` 使其变成仅可写却不显示的字段。
9597

@@ -100,6 +102,7 @@ class ArticleSerializer(ArticleBaseSerializer):
100102

101103
...
102104

105+
# 注意继承的父类是 ArticleBaseSerializer
103106
class ArticleDetailSerializer(ArticleBaseSerializer):
104107
# 渲染后的正文
105108
body_html = serializers.SerializerMethodField()
@@ -117,10 +120,12 @@ class ArticleDetailSerializer(ArticleBaseSerializer):
117120
fields = '__all__'
118121
```
119122

120-
`body_html``toc_html` 这两个渲染后的字段是经过加工后的数据,不存在于原始的数据中。为了将这类只读的附加字段添加到接口里,就可以用到 `SerializerMethodField()` 字段了。比如说 `body_html` 字段,它会自动去调用 `get_body_html()` 方法,并将其返回结果作为需要序列化的数据。方法中的 `obj` 参数是序列化器获取到的 model 实例,也就是文章对象了。
123+
`body_html``toc_html` 这两个渲染后的字段是经过加工后的数据,不存在于原始的数据中。为了将这类只读的附加字段添加到接口里,就可以用到 `SerializerMethodField()` 字段了。比如说上面代码中的 `body_html` 字段,它会自动去调用 `get_body_html()` 方法,并将其返回结果作为需要序列化的数据。方法中的 `obj` 参数是序列化器获取到的 model 实例,也就是文章对象了。
121124

122125
这样就大功告成了,读者自己测试一下,顺利的话详情接口就可以返回 Markdown 渲染后的数据了。
123126

127+
> 记得原始文本应该用 Markdown 语法编写。成功的话 `body_html` 字段返回的是带有 html 标签的文本。
128+
>
124129
> 代码重构得太早可能会导致某些不必要的抽象,太晚又可能堆积太多”屎山“而无从下手。理想情况下的重构是随着项目的开发同时进行的,在合适的节点进行合适的抽象,看着代码逐渐规整,你也会相当有成就感。
125130
126131
另一个问题是,有时候你可能出于版权方面的考虑不愿意将原始的 Markdown 文章数据给任意用户,那么这里只要做一次鉴权,根据用户的权限选用不同的序列化器即可。(非管理员不返回原始文章数据)

0 commit comments

Comments
 (0)