一扔字、序列化
創(chuàng)建模型類:
創(chuàng)建一個(gè)Snippet模型類革为,用于儲(chǔ)存代碼段舵鳞,編寫snippets/models.py:
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS]) # 得到所有的編程語言
STYLE_CHOICES = sorted((item, item) for item in get_all_styles()) # 得到所有的配色風(fēng)格
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
ordering = ('created',)
遷移到數(shù)據(jù)庫(kù):
python manage.py makemigrations snippets
python manage.py migrate
創(chuàng)建序列化類
首先解釋一下序列化:在這里可以先簡(jiǎn)單的理解為serializer把模型實(shí)例轉(zhuǎn)化為json格式然后響應(yīng)出去,這樣便于客戶端調(diào)用時(shí)解析使用抛虏。
那么反序列化其實(shí)道理差不多,反序列化之后的數(shù)據(jù)格式更便于后臺(tái)使用迂猴。
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
# 利用字段標(biāo)志控制序列化器渲染到HTML頁(yè)面時(shí)的的顯示模板
code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
# 給定經(jīng)過驗(yàn)證的數(shù)據(jù),創(chuàng)建并返回一個(gè)新的 Snippet 實(shí)例
def create(self, validated_data):
return Snippet.objects.create(**validated_data)
# 給定經(jīng)過驗(yàn)證的數(shù)據(jù)峰髓,更新并返回一個(gè)已經(jīng)存在的 Snippet 實(shí)例
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.code = validated_data.get('code', instance.code)
instance.linenos = validated_data.get('linenos', instance.linenos)
instance.language = validated_data.get('language', instance.language)
instance.style = validated_data.get('style', instance.style)
instance.save()
return instance
使用ModelSerializers
在上面的SnippetSerializer類中息尺,我們繼承的是serializers.Serializer類,可以看到SnippetSerializer類中有很多代碼其實(shí)是和models.py中的Snippet模型類似一樣的搂誉,所以這里我們可以改進(jìn)一下。就像在Django中提供了Form類和ModelForm類一樣,django-rest-framework為我們提供了Serializer類和ModelSerializer類巴柿。利用它可以讓我們的代碼簡(jiǎn)潔很多,修改serializers.py:
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
編寫常規(guī)的Django視圖
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
# Create your views here.
@csrf_exempt
def snippet_list(request):
"""
列出所有已經(jīng)存在的snippet或者創(chuàng)建一個(gè)新的snippet
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return JsonResponse(serializer.data, safe=False)
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = SnippetSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
return JsonResponse(serializer.errors, status=400)
@csrf_exempt
def snippet_detail(request, pk):
"""
檢索查看凯旋、更新或者刪除一個(gè)代碼段
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return JsonResponse(serializer.data)
elif request.method == 'PUT':
data = JSONParser().parse(request)
serializer = SnippetSerializer(snippet, data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
return JsonResponse(serializer.errors, status=400)
elif request.method == 'DELETE':
snippet.delete()
return HttpResponse(status=204)
配置urls:
先創(chuàng)建snippets/urls.py
from django.conf.urls import url
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]
接著就是tutorial/urls.py
from django.conf.urls import url, include
urlpatterns = [
url(r'^', include('snippets.urls')),
]
二至非、請(qǐng)求和響應(yīng)
精簡(jiǎn)view.py中的代碼:
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
@api_view(['GET', 'POST'])
def snippet_list(request):
"""
列出所有已經(jīng)存在的snippet或者創(chuàng)建一個(gè)新的snippet
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
"""
Retrieve, update or delete a snippet instance.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
和上一個(gè)版本對(duì)比有如下不同:
- Request對(duì)象:
平時(shí)我們?cè)趯慏jango的視圖函數(shù)的時(shí)候,都會(huì)帶上一個(gè)request參數(shù)舰蟆,這樣就能處理平時(shí)搭建網(wǎng)站時(shí)身害,瀏覽器訪問網(wǎng)頁(yè)時(shí)發(fā)出的常規(guī)的HttpRequest塌鸯。但是現(xiàn)在我們導(dǎo)入了django-rest-framework丙猬,它能夠?qū)equest進(jìn)行拓展,并且提供更靈活的請(qǐng)求解析咐低。這個(gè)特性體現(xiàn)在哪呢见擦?請(qǐng)看下面這個(gè)例子:
request.POST # 只能處理表單數(shù)據(jù).只能處理POST請(qǐng)求
request.data # 能處理各種數(shù)據(jù)损痰。 可以處理'POST', 'PUT' 和 'PATCH'模式的請(qǐng)求
拓展后的request使用request.data就可以處理各種各樣的請(qǐng)求了酒来,而原本的request在處理時(shí)需要指定請(qǐng)求模式堰汉。
- Response對(duì)象:
和request對(duì)象一樣,django-rest-framework也對(duì)其進(jìn)行了很實(shí)用的拓展翘鸭,在上一個(gè)版本中,我們導(dǎo)入了JsonResponse用于返回json格式的響應(yīng)就乓。
也就是說,在return的時(shí)候就需要指明json格式噩翠,這樣顯得很不實(shí)用而且很單一,所以經(jīng)過拓展后的Reponse對(duì)象就很方便了伤锚,它會(huì)根據(jù)客戶端的請(qǐng)求頭部信息來確定正確的內(nèi)容類型以返回給客戶端。只需如下代碼:
return Response(data)
狀態(tài)碼
裝飾API視圖
三见芹、基于類的視圖CBV(Class Basic View)
這里要做的是把基于方法的視圖改為基于類的視圖蠢涝,我們將會(huì)了解到APIView
重構(gòu)一下snippets/view.py:
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class SnippetList(APIView):
"""
列出所有已經(jīng)存在的snippet或者創(chuàng)建一個(gè)新的snippet
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
和原來的相比,可以發(fā)現(xiàn)基于類的視圖把各種不同的HTTP請(qǐng)求分離開變成單個(gè)的方法和二,而不是if...elif...這樣的結(jié)構(gòu),所以這樣處理起來很更加的高效惕它。
class SnippetDetail(APIView):
"""
檢索查看、更新或者刪除一個(gè)snippet
"""
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
改為基于類的視圖之后淹魄,修改一下路由了,對(duì)snippets/urls.py稍加修改:
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view()),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]
使用Mixins類+GenericAPIView類
rom snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics
class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
新的視圖類中繼承了 generic.GenericAPIView兆蕉、mixins.ListModelMixin和mixins.CreatteModelMixin缤沦,mixins類為我們提供了list()和create()方法,當(dāng)然缸废,使用這兩個(gè)函數(shù)需要先重載GenericAPIView中的queryset和serializer_class屬性,這點(diǎn)我們查看一下mixins的源碼就可以看出來了企量。比如list方法會(huì)分別通過get_queryset()和get_serializer()得到查詢集和序列化器,其他封裝好的方法也是如此届巩。
修改另外一個(gè)視圖:
class SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
使用通用的視圖類
進(jìn)一步精簡(jiǎn)代碼:
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
通過源碼可知,ListCreateAPIView和RetrieveUpdateDestroyAPIView只是對(duì)上一個(gè)版本代碼的簡(jiǎn)單封裝。
四冒嫡、ViewSets和Routers
在第一部分和第二部分中寫的編寫Django視圖時(shí),使用的都是基于函數(shù)的方法孝凌,并且每個(gè)視圖函數(shù)之前都會(huì)加一個(gè)django-rest-framework帶的裝飾器@api_view。然后在第三部分蟀架,我們就開始把基于函數(shù)的視圖改成了基于類的視圖,然后發(fā)現(xiàn)這樣做視圖部分減少了很多代碼量片拍。
使用ViewSet重構(gòu)視圖
拿本項(xiàng)目為例子,我們之前查看所有snippet列表就要寫一個(gè)視圖類SnippetList苫纤,并在urls.py中為其設(shè)置一個(gè)模式然后as_view使用它,然后要看單個(gè)用戶的詳情頁(yè)就要再寫一個(gè)SnippetDetail視圖類并再在添加一個(gè)url模式污筷。同時(shí)注意到這兩個(gè)視圖類都是繼承的generics.XXXAPIView。而使用ViewSets我們就可以把SnippetList和SnippetDetail合并成SnippetViewSet視圖類耿焊,并且繼承的類改為viewsets.ModelViewSet器腋,這樣就是一個(gè)視圖集了纫塌。
class SnippetViewSet(viewsets.ModelViewSet):
"""
viewset自動(dòng)提供了`list`, `create`, `retrieve`, `update` 和 `destroy` 動(dòng)作.
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
將ViewSets明確的綁定到URL
每個(gè)視圖集的url模式都需要我們?cè)赼s_view中傳入?yún)?shù)避除,把snippets/urls.py的代碼換成下面的:
from django.conf.urls import url,include
from snippets.views import SnippetViewSet
snippet_list = SnippetViewSet.as_view({
'get': 'list',
'post': 'create'
})
snippet_detail = SnippetViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
})
urlpatterns = format_suffix_patterns([
url(r'^snippets/$', snippet_list, name='snippet-list'),
url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
使用Routers
from django.conf.urls import url, include
from snippets import views
from rest_framework.routers import DefaultRouter
# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
# The API URLs are now determined automatically by the router.
# Additionally, we include the login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
]
五、權(quán)限和認(rèn)證
Object:
- snippets 與User相關(guān)聯(lián)
- 只有登錄的用戶才可以創(chuàng)建snippets
- 只有創(chuàng)建該snippets的用戶有編輯權(quán)限
- 未登錄的用戶只有訪問的權(quán)限
修改Snippets Model
在Snippet模型添加一個(gè)owner字段作為外鍵
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
添加UserSerializer
在snippets/serializers.py中添加一個(gè)User序列化器:
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'snippets')
owner是snippet的外鍵状飞,可以通過Snippet正向查詢到owner字段的數(shù)據(jù),而snippet在User模型中是一個(gè)反向關(guān)系焙糟,User這邊查詢不到用戶創(chuàng)建的snippet酬荞,所以我們需要手動(dòng)為UserSerializer添加snippets字段枪向。
添加相應(yīng)的UserView
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
路由分發(fā)
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
關(guān)聯(lián)Snippets和Users
如果像之前那樣創(chuàng)建代碼段的話,我們還不能把Snippets和Users關(guān)聯(lián)起來深员。因?yàn)樵谑褂玫臅r(shí)候User的數(shù)據(jù)是通過request傳入的,而不是以序列化的數(shù)據(jù)傳遞過來叠赐。
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
這個(gè)perform_create() 可以讓用戶在通過POST請(qǐng)求創(chuàng)建一個(gè)新的Snippet時(shí),會(huì)把request中的user賦值給Snippet的owner惩嘉。
為了提高可讀性惹苗,在API中要讓owner顯示用戶名桩蓉,所以我們?cè)赟nippetSerializer 下面增加一個(gè)字段:
owner = serializers.CharField(read_only=True, source='owner.username')
source
參數(shù)指明顯示owner模型的哪一個(gè)字段
添加權(quán)限
現(xiàn)在Snippet和User已經(jīng)關(guān)聯(lián)起來并且是可瀏覽的玷或。接下來實(shí)現(xiàn)權(quán)限判斷:
from rest_framework import permissions
# 在SnippetList和SnippetDetail類中加入
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
功能:驗(yàn)證當(dāng)前用戶是否Login片任,若是位他,則可以創(chuàng)建Snippet鹅髓,編輯(包括修改窿冯、刪除)某一個(gè)具體的Snippet执桌,若否,則只能瀏覽Snippet膘壶。
添加對(duì)象權(quán)限
接著我們要實(shí)現(xiàn)的是每個(gè)Snippet只有其創(chuàng)建者才可以對(duì)其進(jìn)行更改香椎、刪除等操作畜伐。因此,我們需要設(shè)置一下自定義權(quán)限慎框,使每個(gè)Snippet只允許其創(chuàng)建者編輯它笨枯。在snippets目錄下新建一個(gè)permissions.py:
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
使每個(gè)Snippet只允許其創(chuàng)建者編輯它
"""
def has_object_permission(self, request, view, obj):
# 任何用戶或者游客都可以訪問任何Snippet粱檀,所以當(dāng)請(qǐng)求動(dòng)作在安全范圍內(nèi)茄蚯,
# 也就是GET,HEAD汗盘,OPTIONS請(qǐng)求時(shí)衡未,都會(huì)被允許
if request.method in permissions.SAFE_METHODS:
return True
# 而當(dāng)請(qǐng)求不是上面的安全模式的話绊诲,那就需要判斷一下當(dāng)前的用戶
# 如果Snippet所有者和當(dāng)前的用戶一致,那就允許,否則返回錯(cuò)誤信息
return obj.owner == request.user