DRF 框架
介绍
推荐1:前往官方文档
推荐2: bilibili 教程
Restful API 最佳实践
- 协议
- 域名
- 版本
- 路径
- HTTP动词
- 过滤信息(Filtering)
- 状态码(Status Codes)
- 错误处理(Error handling)
- 返回结果
- Hypermedia API
HTTP 请求方法详解
GET (SELECT)
: 从服务器取出资源(一项或多项)POST (CREATE)
: 在服务器新建一个资源PUT (UPDATE)
: 在服务器更新资源(客户端提供完整资源)PATCH (UPDATE)
: 在服务器更新资源(客户端提供部分资源)DELETE (DELETE)
: 从服务器删除资源HEAD
: 获取资源的元数据OPTIONS
: 获取信息,关于资源的哪些属性是客户端可以改变的
创建项目
bash
$ pip install django djangorestframework # 安装项目依赖
$ django-admin startproject drf_tutorial # 创建项目
$ cd drf_tutorial # 进入项目目录
$ python manage.py startapp course # 创建一个课程应用
# 先去修改 `settings.py`, 配置文件完整信息全部在下面: (统一接下来的规范)
$ python manage.py makemigrations # 生成迁移文件
$ python manage.py migrate # 迁移数据
$ python manage.py createsuperuser # 创建后台超级管理员
$ python manage.py runserver localhost:8000 # 启动数据库
- 修改
settings.py
配置文件信息如下:
详情:
python
"""
Django settings for drf_tutorial project.
Generated by 'django-admin startproject' using Django 5.0.6.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
import os.path
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-7m855cg9s1&vk*m7r3emn4_mjvr3yw=ygn+9o2)m*j#jt2&=ph"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework", # 添加 rest_framework 框架
"rest_framework.authtoken", # 启用 DRF 自带的Token认证, 需要迁移数据, 生成存储 token 的表
"course", # 添加应用
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "drf_tutorial.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "drf_tutorial.wsgi.application"
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/
LANGUAGE_CODE = "zh-hans"
TIME_ZONE = "Asia/Shanghai"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = "static/"
# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
STATIC_ROOT = BASE_DIR / "static"
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'staticfiles'),
]
# DRF的全局配置
REST_FRAMEWORK = {
# DRF 自带的分页的类
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
# 每页显示的个数
"PAGE_SIZE": 10,
# 时间显示的格式
"DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S", # 年月日 时分秒
# drf 返回 response 的时候, 使用哪一个 render 类
"DEFAULT_RENDERER_CLASSES": [
"rest_framework.renderers.JSONRenderer",
"rest_framework.renderers.BrowsableAPIRenderer",
],
# 如何解析 Request
"DEFAULT_PARSER_CLASSES": [
"rest_framework.parsers.JSONParser", # 解析 json
"rest_framework.parsers.FormParser", # 解析 form
"rest_framework.parsers.MultiPartParser", # 解析文件
],
# 权限认证方式
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
# "rest_framework.permissions.IsAdminUser",
],
# 认证方式
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.BasicAuthentication", # 用户名密码认证
"rest_framework.authentication.SessionAuthentication", # session 认证
"rest_framework.authentication.TokenAuthentication", # token 认证, 如果使用 token 认证, 就需要在 app 中启用rest自带的Token认证功能
],
}
python
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('api-auth/', include('rest_framework.urls')), # drf 的登录退出功能
path("admin/", admin.site.urls),
]
直接 copy 覆盖自己的配置文件即可
DRF 组件模块
创建模型
详情:
python
from django.db import models
from django.conf import settings
class Course(models.Model):
name = models.CharField(max_length=255, unique=True, help_text='课程信息', verbose_name='课程信息')
introduction = models.TextField(help_text='课程介绍', verbose_name='课程介绍')
teacher = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, help_text='课程讲师',
verbose_name='讲师')
price = models.DecimalField(max_digits=6, decimal_places=2, help_text='课程价格', verbose_name='课程价格')
create_at = models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')
update_at = models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')
class Meta:
verbose_name = '课程信息'
verbose_name_plural = verbose_name
ordering = ('price', )
def __str__(self):
return self.name
python
from django.contrib import admin
from .models import Course
@admin.register(Course)
class CourseAdmin(admin.ModelAdmin):
list_display = ('name', 'price', 'introduction', 'teacher')
search_fields = list_display
list_filter = list_display
⚠️ 执行一下数据库迁移
可以在pycharm的控制台通过下面的方式调试模型类
python
from course.models import Course
from django.core import serializers
serializers.serialize('json', Course.objects.all()) # 获取所有课程信息的序列化结果
⚠️django自带的序列化的不足
- 验证处理 request.data
- 验证器的参数
- 同时序列化多个对象
- 序列化的过程中添加上下文
- 无效的数据异常处理
DRF却包括了以上所有功能
DRF视图开发RESTful API接口
- 原生django开发视图
python
# views.py
import json
from django.http import HttpResponse, JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
course_dict = {
'name': '课程名称',
'introduction': '课程介绍',
'price': 11.1
}
# Django原生的 FBV 编写API接口
@csrf_exempt
def course_list(request):
if request.method == 'GET':
# return HttpResponse(json.dumps(course_dict, content_type='application/json'))
return JsonResponse(course_dict) # 两者等价
if request.method == 'POST':
course = json.loads(request.body.decode('utf-8'))
# return JsonResponse(course, safe=False)
return HttpResponse(json.dumps(course, content_type='application/json'))
# Django CBV 编写API接口
@method_decorator(csrf_exempt, name='dispatch')
class CourseList(View):
def get(self, request):
return JsonResponse(course_dict)
@csrf_exempt
def post(self, request):
course = json.loads(request.body.decode('utf-8'))
return HttpResponse(json.dumps(course), content_type='application/json')
# 分页、排序、认证、权限、限流等等
函数式编程 Function Based View
类视图 Classed Based View
通用类视图 Generic Classed Based View
DRF的视图集 viewsets
from drf_tutorial.urls import urlpatterns
DRF API开发教程
移除所有第三方包依赖
bash
$ pip freeze | xargs pip uninstall -y # 卸载所有第三方包
$ pip cache purge # 清理包下载缓存
使用原生 Django 开发API接口
python
import json
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.views import View
# Django FBV 编写API接口
@csrf_exempt
def course_list(request):
course_dict = {
'name': 'Django',
'price': 0.11
}
if request.method == 'GET':
return HttpResponse(json.dumps(course_dict), content_type='application/json')
# return JsonResponse(course_dict)
if request.method == 'POST':
course = json.loads(request.body.decode('utf-8'))
return HttpResponse(json.dumps(course), content_type='application/json')
# return JsonResponse(course, safe=False)
# Django CBV 编写API接口
@method_decorator(csrf_exempt, name='dispatch')
class CourseList(View):
"""
编写对应的方法执行的视图函数
"""
def dispatch(self, request, *args, **kwargs):
print('这个方法用于根据请求方法来控制视图分发')
return super().dispatch(request, *args, **kwargs)
def get(self, request):
pass
def post(self, request):
pass
使用Django原生的方式编写API接口, 很多东西需要从零开始实现, 比如: 分页、排序、认证、权限、限流等
使用 Django REST Framework 开发API接口
python
from rest_framework.decorators import api_view
"""一、函数式编程: Function Based View"""
@api_view(['GET', 'POST'])
def course_list(request):
"""
获取所有课程或新增一个课程
"""
course_dict = {
'name': 'Django',
'price': 0.99
}
if request.method == 'GET':
pass
elif request.method == 'POST':
pass
使用 DRF的APIView来开发接口
1. 创建模型
python
# todo/models.py
from django.db import models
class Todo(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
done = models.BooleanField(default=False)
def __str__(self):
return self.name
2. 创建视图
- 2.1 使用
Python
注释风格编写API文档描述
python
# todo/views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from rest_framework import status
from .models import Todo
class TodoSerializer(serializers.Serializer):
""" 某个模型的序列化器 """
id = serializers.IntegerField()
name = serializers.CharField(max_length=32)
done = serializers.BooleanField()
def create(self, validated_data):
# 将实例对象返回出去
instance = Todo.objects.create(**validated_data)
return instance
def update(self, instance, validated_data):
# 更新实例对象
Todo.objects.filter(pk=instance.pk).update(**validated_data)
return instance
class TodosView(APIView):
"""
查询集列表数据 & 创建新对象
"""
def get(self, request):
"""
获取查询集数据列表
"""
# 查询实例对象列表
todos = Todo.objects.all()
# 序列化实例对象列表, 传入 instance 表示要将查询集进行序列化为 JSON 数据
serializer = TodoSerializer(instance=todos, many=True)
return Response(serializer.data)
def post(self, request):
"""
创建一条新对象
"""
# 没有传 instance 参数, 会走新建对象的逻辑
serializer = TodoSerializer(data=request.data)
# 对字段规则进行校验
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
# 返回字段校验错误信息
return Response(serializer.errors)
class TodoDetailView(APIView):
"""
单条数据对象: 查 & 删 & 改
GET: 查询单条数据对象
PUT: 更新单条数据对象
DELETE: 删除单条数据对象
"""
def get(self, request, pk):
instance = Todo.objects.get(pk=pk)
serializer = TodoSerializer(instance=instance)
return Response(serializer.data)
def put(self, request, pk):
todo = Todo.objects.get(pk=pk)
# 创建只需要传入 data 参数, 更新需要传入更新的参考数据源和需要更新的实例对象
serializer = TodoSerializer(instance=todo, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.validated_data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
Todo.objects.get(pk=pk).delete()
return Response()
- 2.2 使用
markdown
风格编写API文档描述(需额外配置)
安装依赖
bash
$ pip install markdown
编写 & 配置渲染器
python
# APIRenderers
# todo/renderers.py
from rest_framework.renderers import BaseRenderer
import markdown
class MarkdownRenderer(BaseRenderer):
media_type = 'text/html'
format = 'html'
charset = 'utf-8'
def render(self, data, media_type=None, renderer_context=None):
# 将 Markdown 转换为 HTML
return markdown.markdown(data)
python
# settings.py
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
'todo.renderers.MarkdownRenderer' # 添加自定义渲染器
],
}
- API示例代码
python
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from rest_framework import status
from .models import Todo
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = ('id', 'name', 'done')
class TodosView(APIView):
"""
Todo 列表操作
- **GET**: 获取所有 Todo 项的列表。
- **POST**: 创建一个新的 Todo 项。
### GET 请求
- **URL**: `/todos/`
- **响应**:
- **200 OK**: 返回一个包含所有 Todo 项的列表。
### POST 请求
- **URL**: `/todos/`
- **请求体**:
- `name`: Todo 项的名称 (必填)
- `done`: 是否已完成 (可选,默认为 False)
- **响应**:
- **201 Created**: 创建成功,返回新创建的 Todo 项的信息。
- **400 Bad Request**: 请求体验证失败。
"""
def get(self, request):
"""
获取所有 Todo 项的列表。
- **URL**: `/todos/`
- **响应**:
- **200 OK**: 返回一个包含所有 Todo 项的列表。
"""
todos = Todo.objects.all()
serializer = TodoSerializer(instance=todos, many=True)
return Response(serializer.data)
def post(self, request):
"""
创建一个新的 Todo 项。
- **URL**: `/todos/`
- **请求体**:
- `name`: Todo 项的名称 (必填)
- `done`: 是否已完成 (可选,默认为 False)
- **响应**:
- **201 Created**: 创建成功,返回新创建的 Todo 项的信息。
- **400 Bad Request**: 请求体验证失败。
"""
serializer = TodoSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class TodoDetailView(APIView):
"""
Todo 项的详细操作
- **GET**: 获取指定 ID 的 Todo 项详情。
- **PUT**: 更新指定 ID 的 Todo 项。
- **DELETE**: 删除指定 ID 的 Todo 项。
### GET 请求
- **URL**: `/todos/<int:pk>/`
- **响应**:
- **200 OK**: 返回指定 Todo 项的详细信息。
### PUT 请求
- **URL**: `/todos/<int:pk>/`
- **请求体**:
- `name`: Todo 项的名称 (可选)
- `done`: 是否已完成 (可选)
- **响应**:
- **200 OK**: 更新成功,返回更新后的 Todo 项信息。
- **400 Bad Request**: 请求体验证失败。
### DELETE 请求
- **URL**: `/todos/<int:pk>/`
- **响应**:
- **204 No Content**: 删除成功。
"""
def get(self, request, pk):
"""
获取指定 ID 的 Todo 项详情。
- **URL**: `/todos/<int:pk>/`
- **响应**:
- **200 OK**: 返回指定 Todo 项的详细信息。
"""
instance = Todo.objects.get(pk=pk)
serializer = TodoSerializer(instance=instance)
return Response(serializer.data)
def put(self, request, pk):
"""
更新指定 ID 的 Todo 项。
- **URL**: `/todos/<int:pk>/`
- **请求体**:
- `name`: Todo 项的名称 (可选)
- `done`: 是否已完成 (可选)
- **响应**:
- **200 OK**: 更新成功,返回更新后的 Todo 项信息。
- **400 Bad Request**: 请求体验证失败。
"""
todo = Todo.objects.get(pk=pk)
serializer = TodoSerializer(todo, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.validated_data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk):
"""
删除指定 ID 的 Todo 项。
- **URL**: `/todos/<int:pk>/`
- **响应**:
- **204 No Content**: 删除成功。
"""
Todo.objects.get(pk=pk).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
3. 配置路由
python
# todo/urls.py
from django.urls import path, re_path
from .views import TodosView, TodoDetailView
urlpatterns = [
# 查询集列表数据 & 创建新对象
path('todos/', TodosView.as_view()),
# 单条数据对象: 查 & 删 & 改
re_path('todos/(\d+)/', TodoDetailView.as_view())
]
python
# 总路由 urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('admin/', admin.site.urls),
path('todo/', include('todo.urls')),
path('docs/', include_docs_urls(title="API文档配置示例")),
]
生成 API 文档
1. 使用 coreapi
生成 API
文档
- 配置
settings.py
python
# settings.py
INSTALLED_APPS = [
# ...
'todo',
'coreapi',
'rest_framework',
]
# ...
# DRF 的全局配置
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
- 配置路由
python
# 总路由下面添加 API 文档路由
from django.urls import path, include
from rest_framework.documentation import include_docs_urls
urlpatterns = [
# ...
path('api/', include('todo.urls')),
path('docs/', include_docs_urls(title="API文档配置示例")),
]
访问:
http://127.0.0.1:8000/docs/
2. 使用 drf-yasg
生成 API
文档
- 安装依赖包:
bash
$ pip install drf-yasg
- 配置
settings.py
:
python
INSTALLED_APPS = [
# ...
'drf_yasg',
]
- 配置路由:
python
# 总路由下面添加 API 文档路由 urls.py
from django.contrib import admin
from django.urls import path, include, re_path
from rest_framework.documentation import include_docs_urls
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
# 配置 swagger
schema_view = get_schema_view(
openapi.Info(
title="Swagger API",
default_version='v1',
description="API文档配置示例详情描述",
terms_of_service="https://ZhouYu2156.github.io/",
contact=openapi.Contact(email="contact@snippets.local"),
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=[permissions.AllowAny],
)
urlpatterns = [
# ...
path('admin/', admin.site.urls),
path('api/', include('todo.urls')),
path('docs/', include_docs_urls(title="API文档配置示例")),
re_path(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
re_path(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
技术参考: 解锁API文档自动化魔力:探索drf-yasg
技术博客: DRF__自动生成接口文档
技术参考: 【drf 生成接口文档】
推荐阅读: DRF官方文档
推荐阅读: drf-yasg 官方文档