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

開始之前,假設(shè)你已經(jīng)有Django和Django REST framework的一些基礎(chǔ)了

mixins,ViewSet和routers配合使用

minxis的類有5種

  • CreateModelMixin
  • ListModelMixin
  • RetrieveModelMixin
  • UpdateModelMixin
  • DestroyModelMixin

他們分別對應(yīng)了對數(shù)據(jù)庫的增查改刪操作,使用它們的好處是不用重復(fù)寫著相同的業(yè)務(wù)代碼邏輯,因為每個mixins內(nèi)部都寫好了對應(yīng)的邏輯,只需要設(shè)置一下querysetserializer_class就可以了.

ViewSet也有5種,分別是

  • ViewSetMixin
  • ViewSet
  • GenericViewSet
  • ReadOnlyModelViewSet
  • ModelViewSet

一般來說我們只需要用GenericViewSet就可以了.它繼承了ViewSetMixingenerics.GenericAPIView,后者的功能大家都知道,有了它才能設(shè)置querysetserializer_class屬性.重點是ViewSetMixin.

image

它重寫了方法as_view,這個能讓我們注冊url變得更加簡單,還有一個方法是initialize_request,這個方法主要是給action屬性賦值,這個屬性在設(shè)置動態(tài)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 = [
    # 這個已經(jīng)不需要了
    # url(r'apiAddress/$',models, name="models"),
    url(r'^', include(router.urls)),
]

以后再添加url的時候只需要在router里面注冊就行了,urlpattrtns列表不需要做任何改動.
這樣就完成了一個RESTful API的創(chuàng)建, 能夠合理搭配mixins,ViewSetrouters三者的話,就可以超快速地開發(fā)大量的RESTful API!

使用Django REST framework 的過濾功能

一個最簡單的過濾功能, 例如查詢用戶列表,只返回用戶粉絲數(shù)大于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()
        

上面這種方法如果要過濾的字段多的話,就要寫大量繁瑣的業(yè)務(wù)邏輯代碼

如果想以少量的代碼實現(xiàn)功能強大的過濾要用其他方案了,就要使用django-filter來完成.

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

代碼實現(xiàn):

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,)
    # 設(shè)置過濾字段,這里設(shè)置過濾用戶的名字和粉絲數(shù)
    filter_fields = ('name', 'fans_num')

然后使用瀏覽器打開url,

image

就會發(fā)現(xiàn)頁面多了一個過濾器,點開之后輸入信息就可以使用過濾功能了.
這種方式還是用局限性,比如用戶名想用模糊搜索,或者想要查詢粉絲數(shù)大于100小于200的用戶,這種方式是做不到的.這時候可以使用自定義filter來實現(xiàn)!

新建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也可以實現(xiàn),用兩行代碼就完成了

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,例如我們想按照用戶的粉絲數(shù)量進行排序(升序和降序):

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

這樣已經(jīng)完成了...如果想要做更加復(fù)雜的過濾,可以查看django-filter文檔

自定義分頁

from rest_framework.pagination import PageNumberPagination

class UsersPagination(PageNumberPagination):
    # 指定每一頁的個數(shù)
    page_size = 10
    # 可以讓前端來設(shè)置page_szie參數(shù)來指定每頁個數(shù)
    page_size_query_param = 'page_size'
    # 設(shè)置頁碼的參數(shù)
    page_query_param = 'page'
    

class UserViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet):
    # 設(shè)置分頁的class
    pagination_class = UsersPagination

就這樣幾行代碼就搞定了,而且在返回的json中加了總數(shù),下一頁的鏈接和上一頁的鏈接.

回顧我們之前的代碼,在UserViewSet這個類里面,才寫了7行代碼,就已經(jīng)完成了

  • 獲取用戶列表
  • create一個用戶
  • 分頁
  • 搜索
  • 顧慮
  • 排序

這些功能,如果想要獲取指定用戶的具體信息,直接繼承mixins.RetrieveModelMixin就直接做好了...''

權(quán)限認(rèn)證

比如有一些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的用戶認(rèn)證模式

第一步: 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認(rèn)證的ViewSet的類里面設(shè)置

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

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

加上SessionAuthentication是為了在網(wǎng)站上調(diào)試方便

現(xiàn)在注冊登錄有兩種方式:

  • 用戶注冊之后跳轉(zhuǎn)到登錄頁面讓其登錄
  • 用戶注冊之后自動幫他登錄了

第一種情況的話我們無需再做其他操作,第二種情況我們應(yīng)該在用戶注冊之后返回jwt token的字段給前臺,所以要做兩步:

  • 因為返回字段是mixins幫我們做好了,所以我們要重寫對應(yīng)的方法來修改返回字段
  • 需要查看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)
        
        # 在新建用戶保存到數(shù)據(jù)庫之后
        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的相關(guān)操作可以查看文檔

動態(tài)serializers

這個使用之前說過的action屬性就可以很方便的實現(xiàn)

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

比如說我要發(fā)布這篇文章,需要上傳我(用戶)的id才能和這篇文章建立關(guān)聯(lián),但我們這個可以不用前臺來上傳

serializer.py

class XXXSerializer(serializers.ModelSerializer):
    # user默認(rèn)是當(dāng)前登錄的user
    user = serializers.HiddenField(
        default = serializers.CurrentUserDefault()
    )

還有如果返回的字段邏輯比較復(fù)雜,可以用serializer.SerializerMethodField()來完成,例如:

class XXXSerializer(serializers.ModelSerializer):
    xxx = serializer.SerializerMethodField()
    
    # 把邏輯寫在get_的前綴加xxx(字段名),然后返回
    def get_xxx(self, obj):
        # 完成你的業(yè)務(wù)邏輯
        return 

自定義用戶認(rèn)證

Django自帶的登錄是通過usernamepassword來做登錄的,但是現(xiàn)在很多網(wǎng)站或者app用手機號來來當(dāng)做賬號,這個時候就需要自定義用戶認(rèn)證:

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

總結(jié):

人生苦短,我用Python!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 版權(quán): https://github.com/haiiiiiyun/awesome-django-cn Aweso...
    若與閱讀 23,561評論 3 240
  • 利用 Django REST framework 編寫 RESTful API 自動生成符合 RESTful 規(guī)范...
    星丶雲(yún)閱讀 1,799評論 0 2
  • Django: csrf防御機制 csrf攻擊過程 1.用戶C打開瀏覽器,訪問受信任網(wǎng)站A,輸入用戶名和密碼請求登...
    lijun_m閱讀 1,151評論 0 0
  • 前言 本文標(biāo)題為實戰(zhàn),那么希望你已經(jīng)搭建好了環(huán)境。如果沒有,請參考官方文檔進行環(huán)境搭建: 官方教程 通過學(xué)習(xí)這個例...
    CSU_IceLee閱讀 5,290評論 6 12
  • 本人,圓小井,87年生人,馬上30周歲,茫茫歲月轉(zhuǎn)眼奔三,有感慨時光如梭的飛逝,有紀(jì)念過往人生的五味雜陳,有感恩好...
    圓小井不圓閱讀 351評論 0 0

友情鏈接更多精彩內(nèi)容