django_rest_framework 入门笔记:分页,多条件筛选及权限认证设置

django 及 rest_framework 笔记链接如下:
django 入门笔记:环境及项目搭建
django 入门笔记:数据模型
django 入门笔记:视图及模版
django 入门笔记:Admin 管理系统及表单
django 入门笔记:通用视图类重构视图
django_rest_framework 入门笔记:Serializer
django_rest_framework 入门笔记:视图函数重构
django_rest_framework 入门笔记:分页,多条件筛选及权限认证设置
django 自带 user 字段扩展及头像上传

上一部分我们通过基本类重构了 view,那这部分我们继续深入了解下 DRF 的分页,多条件筛选以及 Token 权限认证

一. 接口数据分页

如果说,后台给你返回的数据很多很多,然后又没有做分页(反正我是碰到过),然后就一直卡在加载界面,心好累。所以分页是很有必要的,分页可以全局设置,也可以不同的 view 设置不同的分页。

1. 设置全局分页参数

我们可以在 project 下的 settings.py 文件中加入 REST_FRAMEWORK 字典,设置全局的分页参数

REST_FRAMEWORK = {
    # 配置全局分页类型和每页数量
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 10,
}
2. 不同 view 设置不同分页

我们也可以在不同的 view 下设置不同的分页参数,分页的类我们可以通过继承已有的 Pagination 或者 BasePagination 来写,然后通过 pagination_class 指定

# 自定义 Pagination,每个 Pagination 的属性不同,可以通过源码查看,然后修改需要的属性
from rest_framework.pagination import PageNumberPagination

class StandardPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = "page"
# 将自定义的 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 字典中将过滤器添加进去

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 属性

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

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

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    filter_backends = (DjangoFilterBackend, )
    # 指定筛选类
    filter_class = PostFilter

我们可以通过网址上拼接筛选信息,然后结果如下
多条件筛选效果

DRF 的 filter_backends 还有 SearchFilter,OrderingFilter,DjangoObjectPermissionsFilter 等,有兴趣的可以查看官网 filtering

三. rest_framework 权限设置

到目前为止我们写的接口不设置任何权限上的设置,任何人都可以进行修改,显然不符合某些情况,这部分将对权限方面做些设置。

首先,我们对 model 类进行一些小的改造

# 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 类做些相应的修改

# 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']

现在我们给相应的视图增加访问权限

# 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/ 后有个空格

http Post http://192.168.x.xxx:8080/api/posts/ title="new_post"&......

然后我们会得到一个 json 数据 {"detail": "身份认证信息未提供。"} 显然被拒绝访问了,同样我们操作 DELETE 等操作也是一样,
身份未认证

接着我们通过用户名登陆后再操作

http -a [username]:[password] POST http://192.168.x.xxx:8080/api/posts/ title="new_post"&......

然后我们发现就可以进行操作了,但是目前这个权限有个缺点,就是不是 post 下的 author 登陆后也可以对 post 进行操作修改,我们重新通过继承 BasePermission 重写一个权限类,限制只能由 post 下的 author 进行修改操作

# 创建一个 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 放到相应视图下

class PostViewSet(viewsets.ModelViewSet):
    # .....
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsPostAuthorOrReadOnly)

当我们通过别的用户名对该接口做修改信息的操作,你会被狠狠的拒绝。

四. rest_framework 身份认证

当我们设置权限的时候,我们不可能每个接口都去设置用户登录,所以就涉及用户身份验证,Android App 常用的身份验证是 Token 验证,所以这部分主要讲 TokenAuthentication,rest_framework 的认证还包括许多,可以查看官网Authentication

首先我们需要在 settings.py 文件中配置 TokenAuthentication

# 首先在 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

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 文件中加入如下代码

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 值

from rest_framework.authtoken.views import obtain_auth_token

urlpatterns = [
    url(r'^login/$', obtain_auth_token, name='get_author_token'),
]

配置完后我们可以运行项目,通过 httpie 进行访问调试,注意该页面不允许 GET 访问

http POST http://192.168.x.xxx:8080/api/login/ username=xxx password=xxxxx

然后我们能够查看到返回结果类似如下
用户登录

当我们获取到 token 后保存到 SharePreference 中,每次访问都在请求头带上 token 值,就不需要每次通过账号密码登录才有权限。

例如之前我们做删除等编辑操作都需要用户进行登录

http -a[username]:[password] DELETE http://192.168.x.xxx:8080/api/post/10/

获得 token 后,我们可以通过如下操作,就可以达到相同的效果

http DELETE http://192.168.x.xxx:8080/api/post/10/ "Authorization: Token [your_token_value]"

如果 obtain_auth_token 不满足需求,我们需要返回更多的字段,那我们可以自定义 AuthToken,首先我们先查看 obtain_auth_token 的源码,然后根据源码进行修改

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 方法即可

# 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 的基本内容到这边也基本结束了,希望你能有所收获。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 一说到REST,我想大家的第一反应就是“啊,就是那种前后台通信方式。”但是在要求详细讲述它所提出的各个约束,以及如...
    时待吾阅读 3,339评论 0 19
  • 题记:你问我有哪些进步?我开始成为自己的朋友。 在大江苏,似乎总逃不开雨,不管在家,还是出远门,都一样。隔三差五,...
    旅途七年阅读 253评论 0 1
  • 1968年,美国著名的心理学家罗森塔尔做了一个实验。他从小学每个年级中抽出部分学生,进行所谓的“预测未来发展”的测...
    Suven阅读 300评论 0 1
  • #######Get和Post 的区别 Get:特点:所有的请求的参数都拼接在Url后面,以?分割URL和传输数据...
    MrBrave丶彬彬阅读 249评论 0 1