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è)的 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 等操作也是一樣,接著我們通過(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
當(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é)束了,希望你能有所收獲。