作者:Kuky_xs
博客:https://www.jianshu.com/u/9fcd71535294
系列文章
《django入门:环境及项目搭建》
《django入门:数据模型》
《django入门:视图及模版》
《django入门:Admin管理系统及表单》
《django入门:通用视图类重构视图》
《用django写接口(入门篇)》
《用django写接口(优化篇)》
正文
上一部分我们通过基本类重构了 view,那这部分我们继续深入了解下 DRF 的分页,多条件筛选以及 Token 权限认证
接口数据分页
如果说,后台给你返回的数据很多很多,然后又没有做分页(反正我是碰到过),然后就一直卡在加载界面,心好累。所以分页是很有必要的,分页可以全局设置,也可以不同的 view 设置不同的分页。
1.设置全局分页参数
我们可以在 project 下的 settings.py 文件中加入 REST_FRAMEWORK 字典,设置全局的分页参数
代码语言:javascript复制REST_FRAMEWORK = {
# 配置全局分页类型和每页数量
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 10,
}
2.不同 view 设置不同分页
我们也可以在不同的 view 下设置不同的分页参数,分页的类我们可以通过继承已有的 Pagination 或者 BasePagination 来写,然后通过 pagination_class 指定
代码语言:javascript复制# 自定义 Pagination,每个 Pagination 的属性不同,可以通过源码查看,然后修改需要的属性
from rest_framework.pagination import PageNumberPagination
class StandardPagination(PageNumberPagination):
page_size = 10
page_size_query_param = "page"
代码语言:javascript复制# 将自定义的 pagination 类设置到 pagination_class
class PostViewSet(viewsets.ModelViewSet):
# ....
# 在 rest_framework.pagination 模块中有多种 Pagination,可以根据具体需求选择
# [PageNumberPagination, CursorPagination, DjangoPaginator, LimitOffsetPagination]
# 也可以是自定义的 Pagination 类
pagination_class = StandardPagination
为了方便查看,我把每页设置一条参数,结果页面如下
接口分页效果
我们可以看到接口返回的信息还包含了前一页和后一页的 url 是不是很人性化
接口数据多条件筛选
目前我们的接口要查找特定的信息只能通过 id 来查找,这肯定是不够完善的,这部分将设置接口的多条件查询
首先我们需要安装过滤器的模块 pip install django-filter
然后我们需要将过滤器模块到 settings.py 中的 INSTALLED_APPS 进行注册才可以使用。注册完以后,我们在 REST_FRAMEWORK 字典中将过滤器添加进去
代码语言:javascript复制REST_FRAMEWORK = {
# 配置全局分页类型和每页数量
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
# 配置过滤器
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
基本配置完后我们需要对我们的 viewSet 做些修改,增加一个 filter_backends 属性和 filter_fields 属性
代码语言:javascript复制class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
filter_backends = (DjangoFilterBackend, )
# 使用 title 作为另一个筛选条件
filter_fields = ['title']
然后运行项目,我们可以通过网址 http://192.168.x.xxx:8080/api/posts/?title="xxxxxx"&format=json 进行访问,可以得到筛选的结果。但是有个问题就是只能精确查询才可以,如果你输入的参数不完整,就查询不到,接下来,我们尝试着完成模糊查询。
首先我们要先创建一个 filters.py 文件,用来定义过滤器 filter
代码语言:javascript复制import django_filters
# 自定义过滤器需要继承 django_filters.rest_framework.FilterSet 类来写
class PostFilter(django_filters.rest_framework.FilterSet):
# 定义进行过滤的参数,CharFilter 是过滤参数的类型,过滤器参数类型还有很多,包括
# BooleanFilter,ChoiceFilter,DateFilter,NumberFilter,RangeFilter..等等
# field_name 为筛选的参数名,需要和你 model 中的一致,lookup_expr 为筛选参数的条件
# 例如 icontains 为 忽略大小写包含,例如 NumberFilter 则可以有 gte,gt,lte,lt,
# year__gt,year__lt 等
title = django_filters.CharFilter('title', lookup_expr='icontains')
# 指定筛选的 model 和筛选的参数,其中筛选的参数在前面设置了筛选条件,则根据筛选条件来执行,
# 如果为指定筛选条件,则按照精确查询来执行
class Meta:
model = Post
fields = ['title', 'create_time', 'author']
然后我们在 viewSet 指定 FilterClass
代码语言:javascript复制class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
filter_backends = (DjangoFilterBackend, )
# 指定筛选类
filter_class = PostFilter
我们可以通过网址上拼接筛选信息,然后结果如下
多条件筛选效果
DRF 的 filter_backends 还有 SearchFilter,OrderingFilter,DjangoObjectPermissionsFilter 等,有兴趣的可以查看官网 filtering
http://www.django-rest-framework.org/api-guide/filtering/#example
rest_framework 权限设置
到目前为止我们写的接口不设置任何权限上的设置,任何人都可以进行修改,显然不符合某些情况,这部分将对权限方面做些设置。
首先,我们对 model 类进行一些小的改造
代码语言:javascript复制# models.py
# 省略 import
class Post(models.Model):
# ....省略之前的字段
# 添加 author 字段,author 我们使用 django 自带的 User 类,
# 我们通过 ForeignKey 进行关联两个 Model,related_name 为反向引用,
# 即我们在 User 表内可以通过 related_name 的值来引用 post 对象
author = models.ForeignKey(User, related_name='posts', on_delete=models.CASCADE)
对数据库做迁移工作后我们对 serializer 类做些相应的修改
代码语言:javascript复制# serializers.py
# ...省略 import
class UserSerializer(serializers.ModelSerializer):
# posts 字段是反向引用,必须要显示声明出来才可以
posts = serializers.PrimaryKeyRelatedField(many=True, queryset=Post.objects.all())
class Meta:
model = User
fields = ['id', 'username', 'posts']
class PostSerializer(serializer.ModelSerializer):
# 显示 author 中的某个字段,例如 username,我们可以通过 source 参数设置
author = serializer.ReadOnlyField(source='author.usernam')
class Meta:
model = Post
fields = ['id', 'title', 'body', 'excerpt', 'author', 'create_time', 'modified_time']
现在我们给相应的视图增加访问权限
代码语言:javascript复制# views.py
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
# 通过元组增加权限类,IsAuthenticatedOrReadOnly 类未登录只读或者登陆后无权限只读
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
修改后我们运行项目,并通过 httpie 进行一些读取和修改的操作
http http://192.168.x.xxx:8080/api/posts/ 能够正常返回的 post 列表
接着我们做 post 提交试试, 自行完善参数值,注意在 posts/ 后有个空格
代码语言:javascript复制http Post http://192.168.x.xxx:8080/api/posts/ title="new_post"&......
然后我们会得到一个 json 数据 {"detail": "身份认证信息未提供。"} 显然被拒绝访问了,同样我们操作 DELETE 等操作也是一样
接着我们通过用户名登陆后再操作
代码语言:javascript复制http -a [username]:[password] POST http://192.168.x.xxx:8080/api/posts/ title="new_post"&......
然后我们发现就可以进行操作了,但是目前这个权限有个缺点,就是不是 post 下的 author 登陆后也可以对 post 进行操作修改,我们重新通过继承 BasePermission 重写一个权限类,限制只能由 post 下的 author 进行修改操作
代码语言:javascript复制# 创建一个 permissions.py 文件,然后把我们的权限写在该文件下
class IsPostAuthorOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# 通过源码可以知道 SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
if request.method in permissions.SAFE_METHODS:
return True
# 除了 SAFE_METHOD 外的方法我们通过判断是否为该 post 下对应的 author
return request.user == obj.author
接着我们把自定义的 permission 放到相应视图下
代码语言:javascript复制class PostViewSet(viewsets.ModelViewSet):
# .....
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsPostAuthorOrReadOnly)
当我们通过别的用户名对该接口做修改信息的操作,你会被狠狠的拒绝。
rest_framework 身份认证
当我们设置权限的时候,我们不可能每个接口都去设置用户登录,所以就涉及用户身份验证,Android App 常用的身份验证是 Token 验证,所以这部分主要讲 TokenAuthentication,rest_framework 的认证还包括许多,可以查看官网Authentication
http://www.django-rest-framework.org/api-guide/authentication/
首先我们需要在 settings.py 文件中配置 TokenAuthentication
代码语言:javascript复制# 首先在 INSTALLED_APPS 注册 authtoken
INSTALLED_APPS = [
# ....
'rest_framework',
'rest_framework.authtoken',
]
# 然后在 REST_FRAMEWORK 字典中配置 DEFAULT_AUTHENTICATION_CLASSES
REST_FRAMEWORK = {
# 配置全局为 token 验证
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
)
}
配置完后我们需要做数据库的迁移工作,生成 token 的数据库 python manage.py migrate生成数据库后,我们需要对已经存在的用户生成 token
代码语言:javascript复制from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
users = User.objects.all()
for user in users:
# 生成 token
token, created = Token.objects.get_or_create(user=user)
print user.username, token.key
当然,我们不可能每次创建用户的时候都手动去生成 token,接着我们需要在 models.py 文件中加入如下代码
代码语言:javascript复制from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
接着我们需要配置 url,用于返回 token 值
代码语言:javascript复制from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
url(r'^login/$', obtain_auth_token, name='get_author_token'),
]
配置完后我们可以运行项目,通过 httpie 进行访问调试,注意该页面不允许 GET 访问
http://192.168.x.xxx:8080/api/login/ username=xxx password=xxxxx
然后我们能够查看到返回结果类似如下
当我们获取到 token 后保存到 SharePreference 中,每次访问都在请求头带上 token 值,就不需要每次通过账号密码登录才有权限。
例如之前我们做删除等编辑操作都需要用户进行登录
代码语言:javascript复制http -a[username]:[password] DELETE http://192.168.x.xxx:8080/api/post/10/
获得 token 后,我们可以通过如下操作,就可以达到相同的效果
代码语言:javascript复制http DELETE http://192.168.x.xxx:8080/api/post/10/ "Authorization: Token [your_token_value]"
如果 obtain_auth_token 不满足需求,我们需要返回更多的字段,那我们可以自定义 AuthToken,首先我们先查看 obtain_auth_token 的源码,然后根据源码进行修改
代码语言:javascript复制class ObtainAuthToken(APIView):
# 限流类
throttle_classes = ()
# 权限类
permission_classes = ()
# 解析类
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
# 渲染类
renderer_classes = (renderers.JSONRenderer,)
# 序列化类
serializer_class = AuthTokenSerializer
def post(self, request, *args, **kwargs):
# 获取序列化类实例
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
# 获取序列化实例中的 user 参数,用来创建 token
user = serializer.validated_data['user']
# 创建 token
token, created = Token.objects.get_or_create(user=user)
# 返回 json 渲染
return Response({'token': token.key})
obtain_auth_token = ObtainAuthToken.as_view()
那我们自定义的认证类就可以继承 ObtainAuthToken 来实现,重写 post 方法即可
代码语言:javascript复制# views.py
class CustomAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, context={'request': request})
serializer.is_valid()
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({'token': token.key, 'user_id': user.pk, 'user_name': user.username})
然后在 url 绑定我们自己的认证类即可返回我们需要的字段值啦~ DRF 的基本内容到这边也基本结束了,希望你能有所收获。
最后附上整个项目的地址:blog_project
https://github.com/kukyxs/blog_project