Skip to content

Commit 36f3534

Browse files
author
yangxg
committed
Step15: 自动生成接口文档
1 parent 7861f32 commit 36f3534

File tree

12 files changed

+454
-156
lines changed

12 files changed

+454
-156
lines changed

.idea/HelloDjango-rest-framework-tutorial.iml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/codeStyles/codeStyleConfig.xml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ django-filter = "*"
2020
drf-haystack = "*"
2121
drf-extensions = "*"
2222
django-redis-cache = "*"
23+
drf-yasg = "*"
2324

2425
[requires]
2526
python_version = "3"

Pipfile.lock

Lines changed: 292 additions & 122 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,12 +197,13 @@ $ git clone https://github.com/HelloGitHub-Team/HelloDjango-REST-framework-tutor
197197
8. [文章详情 API](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/97/)
198198
9. [在接口返回Markdown解析后的内容](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/98/)
199199
10. [实现分类、标签、归档日期接口](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/99/)
200-
11. [评论接口](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/100/))
201-
12. [基于 drf-haystack 实现文章搜索接口](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/101/))
202-
13. [加缓存为接口提速](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/102/))
203-
14. [API 版本管理](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/103/))
204-
15. [限制接口访问频率](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/104/))
205-
16. [单元测试](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/105/))
200+
11. [评论接口](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/100/)
201+
12. [基于 drf-haystack 实现文章搜索接口](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/101/)
202+
13. [加缓存为接口提速](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/102/)
203+
14. [API 版本管理](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/103/)
204+
15. [限制接口访问频率](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/104/)
205+
16. [单元测试](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/105/)
206+
17. [自动生成接口文档](https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/106/)
206207

207208
## 公众号
208209
<p align="center">

blog/filters.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
from django_filters import rest_framework as drf_filters
22

3-
from .models import Post
3+
from .models import Category, Post, Tag
44

55

66
class PostFilter(drf_filters.FilterSet):
77
created_year = drf_filters.NumberFilter(
8-
field_name="created_time", lookup_expr="year"
8+
field_name="created_time", lookup_expr="year", help_text="根据文章发表年份过滤文章列表。"
99
)
1010
created_month = drf_filters.NumberFilter(
11-
field_name="created_time", lookup_expr="month"
11+
field_name="created_time", lookup_expr="month", help_text="根据文章发表月份过滤文章列表。"
12+
)
13+
category = drf_filters.ModelChoiceFilter(
14+
queryset=Category.objects.all(),
15+
help_text="根据分类过滤文章列表。",
16+
)
17+
tags = drf_filters.ModelMultipleChoiceFilter(
18+
queryset=Tag.objects.all(),
19+
help_text="根据标签过滤文章列表。",
1220
)
1321

1422
class Meta:

blog/serializers.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
from django.contrib.auth.models import User
2+
from drf_haystack.serializers import HaystackSerializerMixin
23
from rest_framework import serializers
34
from rest_framework.fields import CharField
45

5-
from drf_haystack.serializers import HaystackSerializerMixin
6-
76
from .models import Category, Post, Tag
87
from .utils import Highlighter
98

@@ -56,8 +55,10 @@ class PostRetrieveSerializer(serializers.ModelSerializer):
5655
category = CategorySerializer()
5756
author = UserSerializer()
5857
tags = TagSerializer(many=True)
59-
toc = serializers.CharField()
60-
body_html = serializers.CharField()
58+
toc = serializers.CharField(label="文章目录", help_text="HTML 格式,每个目录条目均由 li 标签包裹。")
59+
body_html = serializers.CharField(
60+
label="文章内容", help_text="HTML 格式,从 `body` 字段解析而来。"
61+
)
6162

6263
class Meta:
6364
model = Post
@@ -87,8 +88,14 @@ def to_representation(self, value):
8788

8889

8990
class PostHaystackSerializer(HaystackSerializerMixin, PostListSerializer):
90-
title = HighlightedCharField()
91-
summary = HighlightedCharField(source="body")
91+
title = HighlightedCharField(
92+
label="标题", help_text="标题中包含的关键词已由 HTML 标签包裹,并添加了 class,前端可设置相应的样式来高亮关键。"
93+
)
94+
summary = HighlightedCharField(
95+
source="body",
96+
label="摘要",
97+
help_text="摘要中包含的关键词已由 HTML 标签包裹,并添加了 class,前端可设置相应的样式来高亮关键。",
98+
)
9299

93100
class Meta(PostListSerializer.Meta):
94101
search_fields = ["text"]

blog/views.py

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
from django.shortcuts import get_object_or_404
2+
from django.utils.decorators import method_decorator
23
from django.views.generic import DetailView, ListView
34
from django_filters.rest_framework import DjangoFilterBackend
5+
from drf_haystack.viewsets import HaystackViewSet
6+
from drf_yasg import openapi
7+
from drf_yasg.inspectors import FilterInspector
8+
from drf_yasg.utils import swagger_auto_schema
9+
from pure_pagination.mixins import PaginationMixin
410
from rest_framework import mixins, status, viewsets
511
from rest_framework.decorators import action
612
from rest_framework.generics import ListAPIView
@@ -10,26 +16,15 @@
1016
from rest_framework.serializers import DateField
1117
from rest_framework.throttling import AnonRateThrottle
1218
from rest_framework_extensions.cache.decorators import cache_response
13-
from rest_framework_extensions.key_constructor.bits import (
14-
ListSqlQueryKeyBit,
15-
PaginationKeyBit,
16-
RetrieveSqlQueryKeyBit,
17-
)
19+
from rest_framework_extensions.key_constructor.bits import ListSqlQueryKeyBit, PaginationKeyBit, RetrieveSqlQueryKeyBit
1820
from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor
1921

2022
from comments.serializers import CommentSerializer
21-
from drf_haystack.viewsets import HaystackViewSet
22-
from pure_pagination.mixins import PaginationMixin
2323

2424
from .filters import PostFilter
2525
from .models import Category, Post, Tag
2626
from .serializers import (
27-
CategorySerializer,
28-
PostHaystackSerializer,
29-
PostListSerializer,
30-
PostRetrieveSerializer,
31-
TagSerializer,
32-
)
27+
CategorySerializer, PostHaystackSerializer, PostListSerializer, PostRetrieveSerializer, TagSerializer)
3328
from .utils import UpdatedAtKeyBit
3429

3530

@@ -125,6 +120,22 @@ class IndexPostListAPIView(ListAPIView):
125120
class PostViewSet(
126121
mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
127122
):
123+
"""
124+
博客文章视图集
125+
126+
list:
127+
返回博客文章列表
128+
129+
retrieve:
130+
返回博客文章详情
131+
132+
list_comments:
133+
返回博客文章下的评论列表
134+
135+
list_archive_dates:
136+
返回博客文章归档日期列表
137+
"""
138+
128139
serializer_class = PostListSerializer
129140
queryset = Post.objects.all()
130141
permission_classes = [AllowAny]
@@ -148,8 +159,14 @@ def list(self, request, *args, **kwargs):
148159
def retrieve(self, request, *args, **kwargs):
149160
return super().retrieve(request, *args, **kwargs)
150161

162+
@swagger_auto_schema(responses={200: "归档日期列表,时间倒序排列。例如:['2020-08', '2020-06']。"})
151163
@action(
152-
methods=["GET"], detail=False, url_path="archive/dates", url_name="archive-date"
164+
methods=["GET"],
165+
detail=False,
166+
url_path="archive/dates",
167+
url_name="archive-date",
168+
filter_backends=None,
169+
pagination_class=None,
153170
)
154171
def list_archive_dates(self, request, *args, **kwargs):
155172
dates = Post.objects.dates("created_time", "month", order="DESC")
@@ -163,6 +180,8 @@ def list_archive_dates(self, request, *args, **kwargs):
163180
detail=True,
164181
url_path="comments",
165182
url_name="comment",
183+
filter_backends=None, # 移除从 PostViewSet 自动继承的 filter_backends,这样 drf-yasg 就不会生成过滤参数
184+
suffix="List", # 将这个 action 返回的结果标记为列表,否则 drf-yasg 会根据 detail=True 将结果误判为单个对象
166185
pagination_class=LimitOffsetPagination,
167186
serializer_class=CommentSerializer,
168187
)
@@ -183,6 +202,13 @@ def list_comments(self, request, *args, **kwargs):
183202

184203

185204
class CategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
205+
"""
206+
博客文章分类视图集
207+
208+
list:
209+
返回博客文章分类列表
210+
"""
211+
186212
serializer_class = CategorySerializer
187213
# 关闭分页
188214
pagination_class = None
@@ -192,6 +218,13 @@ def get_queryset(self):
192218

193219

194220
class TagViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
221+
"""
222+
博客文章标签视图集
223+
224+
list:
225+
返回博客文章标签列表
226+
"""
227+
195228
serializer_class = TagSerializer
196229
# 关闭分页
197230
pagination_class = None
@@ -204,15 +237,53 @@ class PostSearchAnonRateThrottle(AnonRateThrottle):
204237
THROTTLE_RATES = {"anon": "5/min"}
205238

206239

240+
class PostSearchFilterInspector(FilterInspector):
241+
def get_filter_parameters(self, filter_backend):
242+
return [
243+
openapi.Parameter(
244+
name="text",
245+
in_=openapi.IN_QUERY,
246+
required=True,
247+
description="搜索关键词",
248+
type=openapi.TYPE_STRING,
249+
)
250+
]
251+
252+
253+
@method_decorator(
254+
name="retrieve",
255+
decorator=swagger_auto_schema(
256+
auto_schema=None,
257+
),
258+
)
259+
# @method_decorator(
260+
# name="list",
261+
# decorator=swagger_auto_schema(
262+
# operation_description="返回关键词搜索结果",
263+
# filter_inspectors=[PostSearchFilterInspector],
264+
# ),
265+
# )
207266
class PostSearchView(HaystackViewSet):
267+
"""
268+
搜索视图集
269+
270+
list:
271+
返回搜索结果列表
272+
"""
273+
208274
index_models = [Post]
209275
serializer_class = PostHaystackSerializer
210276
throttle_classes = [PostSearchAnonRateThrottle]
211277

212278

213279
class ApiVersionTestViewSet(viewsets.ViewSet): # pragma: no cover
280+
swagger_schema = None
281+
214282
@action(
215-
methods=["GET"], detail=False, url_path="test", url_name="test",
283+
methods=["GET"],
284+
detail=False,
285+
url_path="test",
286+
url_name="test",
216287
)
217288
def test(self, request, *args, **kwargs):
218289
if request.version == "v1":

blogproject/settings/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"django.contrib.staticfiles",
3232
"pure_pagination", # 分页
3333
"haystack", # 搜索
34+
"drf_yasg", # 文档
3435
"rest_framework",
3536
"django_filters",
3637
"blog.apps.BlogConfig", # 注册 blog 应用

blogproject/urls.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
1515
"""
1616
from django.contrib import admin
17-
from django.urls import include, path
18-
from rest_framework import routers
17+
from django.urls import include, path, re_path
18+
from drf_yasg import openapi
19+
from drf_yasg.views import get_schema_view
20+
from rest_framework import permissions, routers
1921

2022
import blog.views
2123
import comments.views
@@ -32,6 +34,19 @@
3234
r"api-version", blog.views.ApiVersionTestViewSet, basename="api-version"
3335
)
3436

37+
schema_view = get_schema_view(
38+
openapi.Info(
39+
title="HelloDjango REST framework tutorial API",
40+
default_version="v1",
41+
description="HelloDjango REST framework tutorial AP",
42+
terms_of_service="",
43+
contact=openapi.Contact(email="zmrenwu@163.com"),
44+
license=openapi.License(name="GPLv3 License"),
45+
),
46+
public=True,
47+
permission_classes=(permissions.AllowAny,),
48+
)
49+
3550
urlpatterns = [
3651
path("admin/", admin.site.urls),
3752
path("search/", include("haystack.urls")),
@@ -42,4 +57,16 @@
4257
path("api/v1/", include((router.urls, "api"), namespace="v1")),
4358
path("api/v2/", include((router.urls, "api"), namespace="v2")),
4459
path("api/auth/", include("rest_framework.urls", namespace="rest_framework")),
60+
# 文档
61+
re_path(
62+
r"swagger(?P<format>\.json|\.yaml)",
63+
schema_view.without_ui(cache_timeout=0),
64+
name="schema-json",
65+
),
66+
path(
67+
"swagger/",
68+
schema_view.with_ui("swagger", cache_timeout=0),
69+
name="schema-swagger-ui",
70+
),
71+
path("redoc/", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
4572
]

comments/views.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ def comment(request, post_pk):
5353

5454

5555
class CommentViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
56+
"""
57+
博客评论视图集
58+
59+
create:
60+
创建博客评论
61+
"""
62+
5663
serializer_class = CommentSerializer
5764

5865
def get_queryset(self): # pragma: no cover

0 commit comments

Comments
 (0)