剛開始寫views.py
模組的程式碼,一般都是用def
定義的函式檢視,不過DRF更推薦使用class
定義的類檢視,這能讓我們的程式碼更符合DRY(Don't Repeat Yourself)設計原則:
使用APIView
rest_framework.views.APIView
是DRF封裝的API檢視,繼承了django.views.generic.base.View
:
我們用它把函式檢視改寫成類檢視,編輯snippets/views.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):
"""
List all snippets, or create a new 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)
class SnippetDetail(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
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)
類檢視的程式碼跟函式檢視是非常類似的,區別在於GET
、POST
等方法是用的函式而不是if
語句,可以更好的解耦程式碼。
改了views.py
程式碼後,需要同時修改snippets/urls.py
:
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
path('snippets/', views.SnippetList.as_view()),
path('snippets/<int:pk>/', views.SnippetDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
為什麼要加個as_view()
方法?
因為path()
的引數必須是可呼叫的,在原始碼中能看到elif callable(view)
:
def _path(route, view, kwargs=None, name=None, Pattern=None):
if isinstance(view, (list, tuple)):
# For include(...) processing.
pattern = Pattern(route, is_endpoint=False)
urlconf_module, app_name, namespace = view
return URLResolver(
pattern,
urlconf_module,
kwargs,
app_name=app_name,
namespace=namespace,
)
# callable判斷
elif callable(view):
pattern = Pattern(route, name=name, is_endpoint=True)
return URLPattern(pattern, view, kwargs, name)
else:
raise TypeError('view must be a callable or a list/tuple in the case of include().')
as_view()
方法返回了一個內部定義的可呼叫函式:
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError(
'The method name %s is not accepted as a keyword argument '
'to %s().' % (key, cls.__name__)
)
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
# 內部定義了可呼叫函式
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
使用mixins
DRF提供了rest_framework.mixins
模組,封裝了類檢視常用的增刪改查方法:
比如新增CreateModelMixin
:
class CreateModelMixin:
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
類檢視繼承了Mixin後,可以直接使用它的.create()
方法,類似的還有.list()
、.retrieve()
、.update()
和.destroy()
。我們按照這個思路來簡化snippets/views.py
程式碼:
from 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)
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)
瞬間少了好多程式碼,真夠DRY的。
什麼是mixin?
維基百科的解釋:
In object-oriented programming languages, a mixin (or mix-in) is a class that contains methods for use by other classes without having to be the parent class of those other classes.
不太好理解。
換句話說,mixin類提供了一些方法,我們不會直接用這些方法,而是把它新增到其他類來使用。
還是有點抽象。
再簡單點說,mixin只不過是實現多重繼承的一個技巧而已。
這下應該清楚了。
使用generics
如果仔細看snippets/views.py
的程式碼,就會發現我們用到了from rest_framework import generics
:
和generics.GenericAPIView
:
這是DRF提供的通用API類檢視,mixins
只提供了處理方法,views.py
中的類要成為檢視,還需要繼承GenericAPIView
,GenericAPIView
繼承了本文第一小節提到的rest_framework.views.APIView
。除了GenericAPIView
,我們還可以用其他的類檢視進一步簡化程式碼:
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
的原始碼:
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset or creating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
真DRY!
東方說
學到這裡,已經開始感受到了Django REST framework的強大之處了,我覺得學一個框架,不僅要看如何使用,還需要了解它的設計思路和底層實現,這樣才能更好的總結為自己的程式設計思想,寫出更漂亮的程式碼。
參考資料:
https://www.django-rest-framework.org/tutorial/3-class-based-views/#tutorial-3-class-based-views
https://stackoverflow.com/questions/533631/what-is-a-mixin-and-why-are-they-useful