django_rest_framework 入門(mén)筆記:分頁(yè),多條件篩選及權(quán)限認(rèn)證設(shè)置

django 及 rest_framework 筆記鏈接如下:
django 入門(mén)筆記:環(huán)境及項(xiàng)目搭建
django 入門(mén)筆記:數(shù)據(jù)模型
django 入門(mén)筆記:視圖及模版
django 入門(mén)筆記:Admin 管理系統(tǒng)及表單
django 入門(mén)筆記:通用視圖類重構(gòu)視圖
django_rest_framework 入門(mén)筆記:Serializer
django_rest_framework 入門(mén)筆記:視圖函數(shù)重構(gòu)
django_rest_framework 入門(mén)筆記:分頁(yè),多條件篩選及權(quán)限認(rèn)證設(shè)置
django 自帶 user 字段擴(kuò)展及頭像上傳

上一部分我們通過(guò)基本類重構(gòu)了 view,那這部分我們繼續(xù)深入了解下 DRF 的分頁(yè),多條件篩選以及 Token 權(quán)限認(rèn)證

一. 接口數(shù)據(jù)分頁(yè)

如果說(shuō),后臺(tái)給你返回的數(shù)據(jù)很多很多,然后又沒(méi)有做分頁(yè)(反正我是碰到過(guò)),然后就一直卡在加載界面,心好累。所以分頁(yè)是很有必要的,分頁(yè)可以全局設(shè)置,也可以不同的 view 設(shè)置不同的分頁(yè)。

1. 設(shè)置全局分頁(yè)參數(shù)

我們可以在 project 下的 settings.py 文件中加入 REST_FRAMEWORK 字典,設(shè)置全局的分頁(yè)參數(shù)

REST_FRAMEWORK = {
    # 配置全局分頁(yè)類型和每頁(yè)數(shù)量
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 10,
}
2. 不同 view 設(shè)置不同分頁(yè)

我們也可以在不同的 view 下設(shè)置不同的分頁(yè)參數(shù),分頁(yè)的類我們可以通過(guò)繼承已有的 Pagination 或者 BasePagination 來(lái)寫(xiě),然后通過(guò) pagination_class 指定

# 自定義 Pagination,每個(gè) Pagination 的屬性不同,可以通過(guò)源碼查看,然后修改需要的屬性
from rest_framework.pagination import PageNumberPagination

class StandardPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = "page"
# 將自定義的 pagination 類設(shè)置到 pagination_class
class PostViewSet(viewsets.ModelViewSet):
    # ....
    # 在 rest_framework.pagination 模塊中有多種 Pagination,可以根據(jù)具體需求選擇
    # [PageNumberPagination, CursorPagination, DjangoPaginator, LimitOffsetPagination]
    # 也可以是自定義的 Pagination 類
    pagination_class = StandardPagination

為了方便查看,我把每頁(yè)設(shè)置一條參數(shù),結(jié)果頁(yè)面如下
接口分頁(yè)效果

我們可以看到接口返回的信息還包含了前一頁(yè)和后一頁(yè)的 url 是不是很人性化

二. 接口數(shù)據(jù)多條件篩選

目前我們的接口要查找特定的信息只能通過(guò) id 來(lái)查找,這肯定是不夠完善的,這部分將設(shè)置接口的多條件查詢

首先我們需要安裝過(guò)濾器的模塊 pip install django-filter

然后我們需要將過(guò)濾器模塊到 settings.py 中的 INSTALLED_APPS 進(jìn)行注冊(cè)才可以使用。注冊(cè)完以后,我們?cè)?REST_FRAMEWORK 字典中將過(guò)濾器添加進(jìn)去

REST_FRAMEWORK = {
    # 配置全局分頁(yè)類型和每頁(yè)數(shù)量
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
    # 配置過(guò)濾器
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}

基本配置完后我們需要對(duì)我們的 viewSet 做些修改,增加一個(gè) filter_backends 屬性和 filter_fields 屬性

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    filter_backends = (DjangoFilterBackend, )
    # 使用 title 作為另一個(gè)篩選條件
    filter_fields = ['title']

然后運(yùn)行項(xiàng)目,我們可以通過(guò)網(wǎng)址 http://192.168.x.xxx:8080/api/posts/?title="xxxxxx"&format=json 進(jìn)行訪問(wèn),可以得到篩選的結(jié)果。但是有個(gè)問(wèn)題就是只能精確查詢才可以,如果你輸入的參數(shù)不完整,就查詢不到,接下來(lái),我們嘗試著完成模糊查詢。

首先我們要先創(chuàng)建一個(gè) filters.py 文件,用來(lái)定義過(guò)濾器 filter

import django_filters

# 自定義過(guò)濾器需要繼承 django_filters.rest_framework.FilterSet 類來(lái)寫(xiě)
class PostFilter(django_filters.rest_framework.FilterSet):
    # 定義進(jìn)行過(guò)濾的參數(shù),CharFilter 是過(guò)濾參數(shù)的類型,過(guò)濾器參數(shù)類型還有很多,包括
    # BooleanFilter,ChoiceFilter,DateFilter,NumberFilter,RangeFilter..等等
    # field_name 為篩選的參數(shù)名,需要和你 model 中的一致,lookup_expr 為篩選參數(shù)的條件
    # 例如 icontains 為 忽略大小寫(xiě)包含,例如 NumberFilter 則可以有 gte,gt,lte,lt,
    # year__gt,year__lt 等
    title = django_filters.CharFilter('title', lookup_expr='icontains')
    
    # 指定篩選的 model 和篩選的參數(shù),其中篩選的參數(shù)在前面設(shè)置了篩選條件,則根據(jù)篩選條件來(lái)執(zhí)行,
    # 如果為指定篩選條件,則按照精確查詢來(lái)執(zhí)行
    class Meta:
        model = Post
        fields = ['title', 'create_time', 'author']

然后我們?cè)?viewSet 指定 FilterClass

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    filter_backends = (DjangoFilterBackend, )
    # 指定篩選類
    filter_class = PostFilter

我們可以通過(guò)網(wǎng)址上拼接篩選信息,然后結(jié)果如下
多條件篩選效果

DRF 的 filter_backends 還有 SearchFilter,OrderingFilter,DjangoObjectPermissionsFilter 等,有興趣的可以查看官網(wǎng) filtering

三. rest_framework 權(quán)限設(shè)置

到目前為止我們寫(xiě)的接口不設(shè)置任何權(quán)限上的設(shè)置,任何人都可以進(jìn)行修改,顯然不符合某些情況,這部分將對(duì)權(quán)限方面做些設(shè)置。

首先,我們對(duì) model 類進(jìn)行一些小的改造

# models.py
# 省略 import
class Post(models.Model):
    # ....省略之前的字段
    # 添加 author 字段,author 我們使用 django 自帶的 User 類,
    # 我們通過(guò) ForeignKey 進(jìn)行關(guān)聯(lián)兩個(gè) Model,related_name 為反向引用,
    # 即我們?cè)?User 表內(nèi)可以通過(guò) related_name 的值來(lái)引用 post 對(duì)象
    author = models.ForeignKey(User, related_name='posts', on_delete=models.CASCADE)

對(duì)數(shù)據(jù)庫(kù)做遷移工作后我們對(duì) serializer 類做些相應(yīng)的修改

# serializers.py
# ...省略 import
class UserSerializer(serializers.ModelSerializer):
    # posts 字段是反向引用,必須要顯示聲明出來(lái)才可以
    posts = serializers.PrimaryKeyRelatedField(many=True, queryset=Post.objects.all())
    
    class Meta:
        model = User
        fields = ['id', 'username', 'posts']
        
class PostSerializer(serializer.ModelSerializer):
    # 顯示 author 中的某個(gè)字段,例如 username,我們可以通過(guò) source 參數(shù)設(shè)置
    author = serializer.ReadOnlyField(source='author.usernam')
    
    class Meta:
        model = Post
        fields = ['id', 'title', 'body', 'excerpt', 'author', 'create_time', 'modified_time']

現(xiàn)在我們給相應(yīng)的視圖增加訪問(wèn)權(quán)限

# views.py
class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    # 通過(guò)元組增加權(quán)限類,IsAuthenticatedOrReadOnly 類未登錄只讀或者登陸后無(wú)權(quán)限只讀
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

修改后我們運(yùn)行項(xiàng)目,并通過(guò) httpie 進(jìn)行一些讀取和修改的操作

http http://192.168.x.xxx:8080/api/posts/ 能夠正常返回的 post 列表

接著我們做 post 提交試試, 自行完善參數(shù)值,注意在 posts/ 后有個(gè)空格

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

然后我們會(huì)得到一個(gè) json 數(shù)據(jù) {"detail": "身份認(rèn)證信息未提供。"} 顯然被拒絕訪問(wèn)了,同樣我們操作 DELETE 等操作也是一樣,
身份未認(rèn)證

接著我們通過(guò)用戶名登陸后再操作

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

然后我們發(fā)現(xiàn)就可以進(jìn)行操作了,但是目前這個(gè)權(quán)限有個(gè)缺點(diǎn),就是不是 post 下的 author 登陸后也可以對(duì) post 進(jìn)行操作修改,我們重新通過(guò)繼承 BasePermission 重寫(xiě)一個(gè)權(quán)限類,限制只能由 post 下的 author 進(jìn)行修改操作

# 創(chuàng)建一個(gè) permissions.py 文件,然后把我們的權(quán)限寫(xiě)在該文件下
class IsPostAuthorOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        # 通過(guò)源碼可以知道 SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
        if request.method in permissions.SAFE_METHODS:
            return True
        # 除了 SAFE_METHOD 外的方法我們通過(guò)判斷是否為該 post 下對(duì)應(yīng)的 author
        return request.user == obj.author

接著我們把自定義的 permission 放到相應(yīng)視圖下

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

當(dāng)我們通過(guò)別的用戶名對(duì)該接口做修改信息的操作,你會(huì)被狠狠的拒絕。

四. rest_framework 身份認(rèn)證

當(dāng)我們?cè)O(shè)置權(quán)限的時(shí)候,我們不可能每個(gè)接口都去設(shè)置用戶登錄,所以就涉及用戶身份驗(yàn)證,Android App 常用的身份驗(yàn)證是 Token 驗(yàn)證,所以這部分主要講 TokenAuthentication,rest_framework 的認(rèn)證還包括許多,可以查看官網(wǎng)Authentication

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

# 首先在 INSTALLED_APPS 注冊(cè) authtoken
INSTALLED_APPS = [
    # ....
    'rest_framework',
    'rest_framework.authtoken',
]

# 然后在 REST_FRAMEWORK 字典中配置 DEFAULT_AUTHENTICATION_CLASSES
REST_FRAMEWORK = {
    # 配置全局為 token 驗(yàn)證
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
    )
}

配置完后我們需要做數(shù)據(jù)庫(kù)的遷移工作,生成 token 的數(shù)據(jù)庫(kù) python manage.py migrate 生成數(shù)據(jù)庫(kù)后,我們需要對(duì)已經(jīng)存在的用戶生成 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

當(dāng)然,我們不可能每次創(chuàng)建用戶的時(shí)候都手動(dòng)去生成 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'),
]

配置完后我們可以運(yùn)行項(xiàng)目,通過(guò) httpie 進(jìn)行訪問(wèn)調(diào)試,注意該頁(yè)面不允許 GET 訪問(wèn)

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

然后我們能夠查看到返回結(jié)果類似如下
用戶登錄

當(dāng)我們獲取到 token 后保存到 SharePreference 中,每次訪問(wèn)都在請(qǐng)求頭帶上 token 值,就不需要每次通過(guò)賬號(hào)密碼登錄才有權(quán)限。

例如之前我們做刪除等編輯操作都需要用戶進(jìn)行登錄

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

獲得 token 后,我們可以通過(guò)如下操作,就可以達(dá)到相同的效果

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

如果 obtain_auth_token 不滿足需求,我們需要返回更多的字段,那我們可以自定義 AuthToken,首先我們先查看 obtain_auth_token 的源碼,然后根據(jù)源碼進(jìn)行修改

class ObtainAuthToken(APIView):
    # 限流類
    throttle_classes = ()
    # 權(quán)限類
    permission_classes = ()
    # 解析類
    parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
    # 渲染類
    renderer_classes = (renderers.JSONRenderer,)
    # 序列化類
    serializer_class = AuthTokenSerializer

    def post(self, request, *args, **kwargs):
        # 獲取序列化類實(shí)例
        serializer = self.serializer_class(data=request.data,
                                           context={'request': request})
        serializer.is_valid(raise_exception=True)
        # 獲取序列化實(shí)例中的 user 參數(shù),用來(lái)創(chuàng)建 token
        user = serializer.validated_data['user']
        # 創(chuàng)建 token
        token, created = Token.objects.get_or_create(user=user)
        # 返回 json 渲染
        return Response({'token': token.key})

obtain_auth_token = ObtainAuthToken.as_view()

那我們自定義的認(rèn)證類就可以繼承 ObtainAuthToken 來(lái)實(shí)現(xiàn),重寫(xiě) 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 綁定我們自己的認(rèn)證類即可返回我們需要的字段值啦~ DRF 的基本內(nèi)容到這邊也基本結(jié)束了,希望你能有所收獲。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評(píng)論 6 540
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,275評(píng)論 3 428
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,904評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,633評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,368評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,736評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,919評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,481評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,235評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,427評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,656評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,055評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,348評(píng)論 1 294
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,160評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,380評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,818評(píng)論 18 139
  • 一說(shuō)到REST,我想大家的第一反應(yīng)就是“啊,就是那種前后臺(tái)通信方式。”但是在要求詳細(xì)講述它所提出的各個(gè)約束,以及如...
    時(shí)待吾閱讀 3,445評(píng)論 0 19
  • 題記:你問(wèn)我有哪些進(jìn)步?我開(kāi)始成為自己的朋友。 在大江蘇,似乎總逃不開(kāi)雨,不管在家,還是出遠(yuǎn)門(mén),都一樣。隔三差五,...
    旅途七年閱讀 269評(píng)論 0 1
  • 1968年,美國(guó)著名的心理學(xué)家羅森塔爾做了一個(gè)實(shí)驗(yàn)。他從小學(xué)每個(gè)年級(jí)中抽出部分學(xué)生,進(jìn)行所謂的“預(yù)測(cè)未來(lái)發(fā)展”的測(cè)...
    Suven閱讀 341評(píng)論 0 1
  • #######Get和Post 的區(qū)別 Get:特點(diǎn):所有的請(qǐng)求的參數(shù)都拼接在Url后面,以?分割URL和傳輸數(shù)據(jù)...
    MrBrave丶彬彬閱讀 266評(píng)論 0 1