什么是DRF
DRF是一个Python语言,能快速写出REST风格api的开发框架,并可以自动生成上图所示的管理界面。
优点
- Python语言简洁,代码量少,有许多middleware方便开发,易于理解
缺点
- 不适合体量特别大的项目
- 国内普及度不如java项目,可能需要自己踩坑自己填坑
目录
- 环境安装
1.1 安装新版本Python
1.2 安装数据库
1.3 安装DRF
1.4 安装常用轮子 - 项目管理
2.1 创建项目
2.2 创建app
2.3 修改设置 - 代码编写
3.1 创建模型
3.2 模型迁移至数据库
3.3 模型序列化
3.4 接口Views
3.4 路由Routers - 发布上线
5.1 测试
5.2 正式发布 - 进阶
5.1 搜索
5.2 前端传参过滤
5.3 前端传参排序
5.4 viewsets的更多功能
5.5 鉴权 - 可能遇到的问题
6.1 跨域
6.2 静态文件无法读取
1.1 安装新版本Python
# 查看python版本
$ python -V
建议安装最新版,看网上有些方法装下来的python版本也不是最新的,可以去官网查看最新版安装链接,下载后解压,安装。
# 下载
$ wget https://www.python.org/ftp/python/3.8.3/Python-3.8.3.tgz
# 解压
$ tar zxvf Python-3.8.3.tgz
$ cd Python-3.8.3/
# 配置
# ./configure
$ ./configure prefix=/usr/local/python3 --enable-optimizations
# 安装
$ make && make install
# 修改环境变量
$ export PATH=$PATH:/usr/local/python3/bin/
# 查看python版本
$ python3 -V
# pip是python的包管理软件,如果没有的话需安装
$ yum -y install python-pip
1.2 安装数据库
mySQL等主流数据库都是支持的, DRF默认用SQLite作为数据库,这里也用SQLite做演示
# 安装SQLite
$ yum install sqlite-devel
1.3 安装Django
(optional)为了使包配置独立于其他的项目,可以创建一个虚拟环境
$ python3 -m venv env
$ source env/bin/activate
要退出虚拟环境,在任意地方deactivate即可
$ deactivate
# 安装Django
$ pip install django
# 安装DRF
$ pip install djangorestframework
1.4 安装常用轮子
$ pip install requests
$ pip install django-filter
$ pip install django-url-filter
2.1 创建项目
# 创建项目
$ django-admin startproject 项目名
2.2 创建app
DRF上project是用来承载各个app,所谓app可以理解为一个个大的功能块,所以需要再创建一个app
# 创建app
$ python manage.py startapp forum
2.3 修改设置
打开根目录下的settings.py
# 修改INSTALLED_APPS
INSTALLED_APPS = [
...
'rest_framework',
'forum', # 刚创建的app
'django_filters', # 筛选用
'requests', # 网络请求用
]
# 修改时区
TIME_ZONE = 'Asia/Shanghai'
# 添加下面的设置
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', # 默认分页类
'PAGE_SIZE': 10, # 每页数目
}
3.1 创建模型
在项目文件夹下,修改models.py
# models.py
class Article(models.Model):
title = models.CharField(max_length=100, default='')
content = models.TextField(default='')
created = models.DateTimeField(auto_now_add=True)
last_modify_date = models.DateTimeField(auto_now=True)
author = models.TextField()
dingId = models.TextField(default='lost')
avatar = models.TextField(default='0')
comments = models.IntegerField(default=0)
reports = models.IntegerField(default=0)
deleted = models.BooleanField(default=False)
class Meta:
db_table = 'article'
3.2 模型迁移至数据库
迁移是在代码中写好model后,终端上执行下面的语句,会自动在数据库中同步对应的字段
$ python manage.py makemigration
$ python manage.py migrate
# 将会在SQLite建立名为article的表,并有model上定义的字段
3.3 模型序列化
# serializers.py
from rest_framework import serializers
from forum.models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
# fields = ('id', 'title', 'content', 'last_modify_date', 'created')
3.4 接口Views
DRF提供了viewsets,下面的4行代码,就实现了文章列表的增删改查
# views.py
from forum.models import Article
from rest_framework import viewsets
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
3.4 路由Routers
viewsets 写好后,为了使前端能够访问,需要在项目下url.py中配置供前端访问的路径
# urls.py
from rest_framework.routers import DefaultRouter
from forum import views
forumPrefix = 'api/forum/'
forumRouter = DefaultRouter()
forumRouter.register('article', views.ArticleViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path(forumPrefix, include(forumRouter.urls)),
]
4.1 测试发布
根目录下运行:
$ python manage.py runserver
Validating models...
0 errors found
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
看到http://127.0.0.1:8000/的话就证明没问题了,把这个地址复制到浏览器,就可以看到管理文章开头的管理界面,前端需要调用的话只需拼上前面urls.py中写好的path即可,如http://127.0.0.1:8000/api/forum/article。
4.1 正式发布
正式发布时,settings.py文件中Debug是要改为false的,否则系统会把所有的请求缓存到内存上,造成进程占用的内存越来越大,而debug=false后,由于静态文件安全性考虑,DRF管理界面css样式就获取不到了,可以临时先通过 --insecure解决
# settings.py
DEBUG = False
$ nohup python3 manage.py runserver --insecure 0.0.0.0:8000 > out.log 2>&1 &
# 查看输出
$ tail -f out.log
至此,这已经是一个完整的并跑起来的后端服务了,但仅这些肯定不够,DRF能做的事情也还有很多
5.1 搜索
# views
search_fields = ['id', 'title', 'content']
5.2 前端传参过滤
# views
from url_filter.integrations.drf import DjangoFilterBackend
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
filter_backends = [DjangoFilterBackend]
filter_fields = ['selected', 'deleted', 'reports', 'type']
比如我想筛选用户A点赞数为5到10的文章,前端可以直接把筛选条件拼在url后,十分好用!
.../forum/articles/?author=A&likes__range=5,10
详细的使用说明在:https://django-url-filter.readthedocs.io/en/latest/
5.3 前端传参排序
# views
from rest_framework import filters
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
filter_backends = [filters.OrderingFilter, DjangoFilterBackend]
filter_fields = ['selected', 'deleted', 'reports', 'type']
ordering_fields = ('id', 'created', 'last_modify_date')
比如要按创建时间倒序排列
.../forum/articles/?ordering=-create
真好用!详细说明在:https://www.django-rest-framework.org/api-guide/filtering/
5.4 用@action为viewsets添加更多功能
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
filter_backends = [filters.OrderingFilter, DjangoFilterBackend, filters.SearchFilter]
ordering_fields = ('id', 'created', 'last_modify_date')
filter_fields = ['selected', 'deleted', 'reports', 'type']
search_fields = ['id', 'title', 'content']
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
@action(detail=True, method=['GET'])
# detail=False时,对所有集合产生效果,method默认为GET可省略
def like(self, request, pk=None):
article = self.get_object()
article.likes += 1
article.save()
return Response(article.likes)
修改viewset的CURD方法
class UserViewSet(viewsets.ViewSet):
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass
5.5 鉴权 Simple JWT
安装Simple JWT
$ pip install djangorestframework-simplejwt
在setting中添加默认鉴权类
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
...
}
在urls.py添加对外接口
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
...
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
...
]
用curl获取token
curl \
-X POST \
-H "Content-Type: application/json" \
-d '{"username": "davidattenborough", "password": "boatymcboatface"}' \
http://localhost:8000/api/token/
...
{
"access":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNDU2LCJqdGkiOiJmZDJmOWQ1ZTFhN2M0MmU4OTQ5MzVlMzYyYmNhOGJjYSJ9.NHlztMGER7UADHZJlxNG0WSi22a2KaYSfd1S-AuT7lU",
"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3ODg5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4"
}
返回的两个token,access默认5分钟过期,过期后用refresh刷新,refresh默认1天过期,可以在settings修改过期时间
# Django project settings.py
from datetime import timedelta
...
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1)
}
详细使用方法:
https://django-rest-framework-simplejwt.readthedocs.io/en/latest/getting_started.html
6.1 跨域
在app下新建middlewares.py
# middlewares.py
from django.utils.deprecation import MiddlewareMixin
class MiddleWare(MiddlewareMixin):
def process_response(self, request, response):
response['Access-Control-Allow-Origin'] = "*"
if request.method == 'OPTIONS':
response['Access-Control-Allow-Methods'] = "POST, GET, DELETE, OPTIONS, DELETE"
response['Access-Control-Max-Age'] = 1728000
response["Access-Control-Allow-Headers"] = "Content-Type, x-requested-with"
return response
return response
修改settings
# settings.py
MIDDLEWARE = [
...
'forum.middlewares.MiddleWare',
]
6.2 静态文件无法读取
前面在正式发布时,添加了--insecure,这样做是不安全的,如过去掉的话会造成管理页面css读取不到的问题。https://docs.djangoproject.com/en/3.0/ref/contrib/staticfiles/#module-django.contrib.staticfiles
我看了半天,最后的处理方法是发布前collectstatic收集静态文件,把整个文件夹丢到服务器上,修改setting里STATIC_URL为服务器上文件夹存放的目录
// 收集静态文件
$ python3 manage.py collectstatic