Django REST framework的一些奇巧淫技(干货!!!)

开始之前,假设你已经有Django和Django REST framework的一些基础了

mixins,ViewSet和routers配合使用

minxis的类有5种

  • CreateModelMixin
  • ListModelMixin
  • RetrieveModelMixin
  • UpdateModelMixin
  • DestroyModelMixin

他们分别对应了对数据库的增查改删操作,使用它们的好处是不用重复写着相同的业务代码逻辑,因为每个mixins内部都写好了对应的逻辑,只需要设置一下querysetserializer_class就可以了.

ViewSet也有5种,分别是

  • ViewSetMixin
  • ViewSet
  • GenericViewSet
  • ReadOnlyModelViewSet
  • ModelViewSet

一般来说我们只需要用GenericViewSet就可以了.它继承了ViewSetMixingenerics.GenericAPIView,后者的功能大家都知道,有了它才能设置querysetserializer_class属性.重点是ViewSetMixin.

image

它重写了方法as_view,这个能让我们注册url变得更加简单,还有一个方法是initialize_request,这个方法主要是给action属性赋值,这个属性在设置动态serializerpermission的时候有很大的用处!之后会写到

所以写一个APIView就变得很简单,如下:

from rest_framework import mixins
from rest_framework import viewsets

class XXXViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):

    queryset = Model.objects.all()
    serializer_class = ModelSerializer

接下来就是配置url了

from appname.views import XXXViewSet

models = XXXViewSet.asview({
    'get': 'list',
    'post': 'create'
})

urlpattrtns = [
    url(r'apiAddress/$',models, name="models"),
]

这样就可以把get请求绑定到list的方法上,post请求就绑定到了create方法.不需要再去重写它们了

其实上面配置url的方法还是过于繁琐,这时候就是router登场了,上面的代码改为:

from rest_framework.routers import DefaultRouter
from appname.views import XXXViewSet

router = DefaultRouter()
router.register(r'apiAddress', XXXViewSet, base_name='apiAddress')

urlpattrtns = [
    # 这个已经不需要了
    # url(r'apiAddress/$',models, name="models"),
    url(r'^', include(router.urls)),
]

以后再添加url的时候只需要在router里面注册就行了,urlpattrtns列表不需要做任何改动.
这样就完成了一个RESTful API的创建, 能够合理搭配mixins,ViewSetrouters三者的话,就可以超快速地开发大量的RESTful API!

使用Django REST framework 的过滤功能

一个最简单的过滤功能, 例如查询用户列表,只返回用户粉丝数大于100的:

class XXXViewSet(mixins.ListModelMixin, mixins.CreateModelMixin,viewsets.GenericViewSet):

    serializer_class = ModelSerializer
    def get_queryset(self):
        fans_min = self.reuqest.query_params.get("fans_min", 0)
        if fans_min:
            return User.objects.filter(fans_num__gt=int(fans_min))
        return User.objects.all()
        

上面这种方法如果要过滤的字段多的话,就要写大量繁琐的业务逻辑代码

如果想以少量的代码实现功能强大的过滤要用其他方案了,就要使用django-filter来完成.

首先 pip install django-filter, 然后把django-filter加到INSTALLED_APPS列表中

代码实现:

from rest_framework import mixins
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
from django.contrib.auth import get_user_model

User = get_user_model()

class UserViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):

    queryset = User.objects.all()
    serializer_class = ModelSerializer
    filter_backends = (DjangoFilterBackend,)
    # 设置过滤字段,这里设置过滤用户的名字和粉丝数
    filter_fields = ('name', 'fans_num')

然后使用浏览器打开url,

image

就会发现页面多了一个过滤器,点开之后输入信息就可以使用过滤功能了.
这种方式还是用局限性,比如用户名想用模糊搜索,或者想要查询粉丝数大于100小于200的用户,这种方式是做不到的.这时候可以使用自定义filter来实现!

新建filter.py

import django_filters
from django.contrib.auth import get_user_model

User = get_user_model()

class UserFilter(django_filters.rest_framework.FilterSet):

    min_fans_num = django_filter.NumberFilter(name='fans_num', lookup_expr='gte')
    max_fans_num = django_filters.NumberFilter(name='fans_num', lookup_expr='lt')
    name = django_filters.CharFilter(name='name',lookup_expr='icontains')
    
    class Meta:
        model = User
        fields = ['name', 'min_fans_num', 'max_fans_num']

然后之前的代码改为:

from .filter import UserFilter

class UserViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):

    queryset = User.objects.all()
    serializer_class = ModelSerializer
    filter_backends = (DjangoFilterBackend,)
    filter_class = UserFilter
    

过滤用户名的话,其实用SearchFilter也可以实现,用两行代码就完成了

filter_backends = (DjangoFilterBackend, SearchFilter)
search_fields = ('name',)

还有一些更加强大的配置

The search behavior may be restricted by prepending various characters to the search_fields.

'^' Starts-with search.

'=' Exact matches.

'@' Full-text search. (Currently only supported Django's MySQL backend.)

'$' Regex search.

For example:

search_fields = ('=username', '=email')

还有一个排序的filter,例如我们想按照用户的粉丝数量进行排序(升序和降序):

filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
ordering_fields = ('fans_num',)

这样已经完成了...如果想要做更加复杂的过滤,可以查看django-filter文档

自定义分页

from rest_framework.pagination import PageNumberPagination

class UsersPagination(PageNumberPagination):
    # 指定每一页的个数
    page_size = 10
    # 可以让前端来设置page_szie参数来指定每页个数
    page_size_query_param = 'page_size'
    # 设置页码的参数
    page_query_param = 'page'
    

class UserViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
    # 设置分页的class
    pagination_class = UsersPagination

就这样几行代码就搞定了,而且在返回的json中加了总数,下一页的链接和上一页的链接.

回顾我们之前的代码,在UserViewSet这个类里面,才写了7行代码,就已经完成了

  • 获取用户列表
  • create一个用户
  • 分页
  • 搜索
  • 顾虑
  • 排序

这些功能,如果想要获取指定用户的具体信息,直接继承mixins.RetrieveModelMixin就直接做好了...''

权限认证

比如有一些API功能,是需要用户登录才能使用可以的
或者比如我要删除我这篇博客,也要验证我是作者才能删除

验证用户是否登录

from rest_framework.permissions import IsAuthenticated

class XXXViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin):
    
    permission_classes = (IsAuthenticated,)
    

验证操作是本人,需要自定义persssion

permissions.py

from rest_framework import permissions

class IsOwnerOrReadOnly(permissions.BasePermission):

    def has_object_permission(self, request, view, object):
        if request.method in permissions.SAFE_METHODS:
            return True

        return object.user == request.user

permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)

使用JWT的用户认证模式

第一步: pip install djangorestframework-jwt

第二步: 在url.py中配置

from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
  ...
  url(r'^api-token-auth/', obtain_jwt_token),
  ...
]

第三步: 在需要jwt认证的ViewSet的类里面设置

from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.authentication import SessionAuthentication

class XXXViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
  authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)

加上SessionAuthentication是为了在网站上调试方便

现在注册登录有两种方式:

  • 用户注册之后跳转到登录页面让其登录
  • 用户注册之后自动帮他登录了

第一种情况的话我们无需再做其他操作,第二种情况我们应该在用户注册之后返回jwt token的字段给前台,所以要做两步:

  • 因为返回字段是mixins帮我们做好了,所以我们要重写对应的方法来修改返回字段
  • 需要查看djangorestframework-jwt的源码找到生成jwt token的方法
from rest_framework_jwt.serializers import jwt_encode_handler, jwt_payload_handler

class UserViewSet(CreateModelMixin, RetrieveModelMixin,UpdateModelMixin,viewsets.GenericViewSet):
    # 重写create方法
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = self.perform_create(serializer)
        
        # 在新建用户保存到数据库之后
        tmp_dict = serializer.data
        # 生成JWT Token
        payload = jwt_payload_handler(user)
        tmp_dict['token'] = jwt_encode_handler(payload)

        headers = self.get_success_headers(serializer.data)
        return Response(tmp_dict, status=status.HTTP_201_CREATED, headers=headers)

更多jwt的相关操作可以查看文档

动态serializers

这个使用之前说过的action属性就可以很方便的实现

class UserViewSet(CreateModelMixin, RetrieveModelMixin,UpdateModelMixin,viewsets.GenericViewSet):
    
    # 这个就不需要了
    #serializer_class = XXXSerializer

    def get_serializer_class(self):
        if self.action == 'create':
            return XXXSerializer
        elif self.action == 'list':
            return XXXSerializer
        return XXXSerializer

一些实用的Serializer fields

比如说我要发布这篇文章,需要上传我(用户)的id才能和这篇文章建立关联,但我们这个可以不用前台来上传

serializer.py

class XXXSerializer(serializers.ModelSerializer):
    # user默认是当前登录的user
    user = serializers.HiddenField(
        default = serializers.CurrentUserDefault()
    )

还有如果返回的字段逻辑比较复杂,可以用serializer.SerializerMethodField()来完成,例如:

class XXXSerializer(serializers.ModelSerializer):
    xxx = serializer.SerializerMethodField()
    
    # 把逻辑写在get_的前缀加xxx(字段名),然后返回
    def get_xxx(self, obj):
        # 完成你的业务逻辑
        return 

自定义用户认证

Django自带的登录是通过usernamepassword来做登录的,但是现在很多网站或者app用手机号来来当做账号,这个时候就需要自定义用户认证:

from django.contrib.auth.backends import ModelBackend

class CustomBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            user = User.objects.get(Q(username=username)|Q(mobile=username))
            # 验证密码是否正确
            if user.check_password():
                return user
        except Exception as e:
            return None

用户注册的时候,如果你在后台查看的是明文,这是因为ModelSerializer在保存的时候直接明文保存了, 解决问题:

serializer.py

class UserRegSerializer(serializers.ModelSerializer):
    
    #重写create方法
    def create():
        user = super(UserRegSerializer,self).create(validated_data=validated_data)
        user.set_password(validated_data["password"])
        user.save()
        return user

或者也可以用django的信号量也可以解决

from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
User = get_user_model()

@receiver(post_save, sender=User)
def create_user(sender, instance=None, created=False, **kwargs):
    if created:
        password = instance.password
        instance.set_password(password)
        instance.save()

然后还要app.py里面配置

from django.apps import AppConfig


class UsersConfig(AppConfig):
    name = 'users'
    
    def ready(self):
        import users.signals

总结:

人生苦短,我用Python!

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

推荐阅读更多精彩内容

  • 版权: https://github.com/haiiiiiyun/awesome-django-cn Aweso...
    若与阅读 22,842评论 3 242
  • 利用 Django REST framework 编写 RESTful API 自动生成符合 RESTful 规范...
    星丶雲阅读 1,653评论 0 2
  • Django: csrf防御机制 csrf攻击过程 1.用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登...
    lijun_m阅读 1,032评论 0 0
  • 前言 本文标题为实战,那么希望你已经搭建好了环境。如果没有,请参考官方文档进行环境搭建: 官方教程 通过学习这个例...
    CSU_IceLee阅读 5,087评论 6 12
  • 本人,圆小井,87年生人,马上30周岁,茫茫岁月转眼奔三,有感慨时光如梭的飞逝,有纪念过往人生的五味杂陈,有感恩好...
    圆小井不圆阅读 298评论 0 0