Django Rest Framework
1 安装
pip install djangorestframework
# settings.py
INSTALLED_APPS = [
...
'rest_framework',
]
2 视图
视图 | 说明 |
---|---|
View | 基础视图 |
APIView | 在View 的基础上进行功能扩展 |
GenericAPIView | 对APIView 一些固定操作进行封装 |
ModelViewSet | 在GenericAPIView 基础上再封装 |
2.0 准备工作
所有的操作基于下面的这张user
表进行操作
class UserModel(models.Model):
id = models.UUIDField(verbose_name='用户id',
auto_created=True,
primary_key=True,
default=uuid.uuid4,
editable=False)
username = models.CharField(verbose_name='用户名', max_length=16)
age = models.IntegerField(verbose_name='年龄')
create_time = models.DateTimeField(verbose_name='注册时间',
auto_created=True,
default=timezone.now)
class Meta:
db_table = 'user'
verbose_name = '用户表'
verbose_name_plural = '用户表'
RESTFul风格的API设计,示例user表的5个接口:
api | 请求方式 | 说明 |
---|---|---|
/user/ | GET | 查询全部用户 |
/user/ | POST | 添加用户 |
/user/ | PUT | 更新用户 |
/user/id/ | GET | 查询某个用户 |
/user/id/ | DELETE | 删除某个用户 |
2.1 View
2.1.1 架子
FBV: Function Based View 的写法
# views.py
def user(request):
if request.method == 'GET':
# 做查询操作
pass
else: # 说明是POST
# 做更新/添加操作
pass
CBV: Class Based View 写法
- views.py
from rest_framework.views import View
# 查所有,添加,更新
class UserView(View):
def get(self, request):
pass
def post(self, request):
pass
def put(self, request):
pass
# 根据id查/删
class UserDetailView(View):
def get(self, request, id):
pass
def delete(self, request, id):
pass
- urls.py
from django.contrib import admin
from django.urls import path, re_path
from blog.views import UserView, UserDetailView
urlpatterns = [
# path('admin/', admin.site.urls),
path('user/', UserView.as_view()),
# 因为本示例中的模型id是uuid,所以正则是这般匹配的
# 如果你的id是整型,可以进行相应的修改,比如r'^user/(?P<id>\d+)/$'
re_path(r'^user/(?P<id>[\w\-]+)/$', UserDetailView.as_view()),
]
2.1.2 View做了什么【源码有省略】
View
内部会判断当前请求方式,然后进行分发。所以,重点在于View.as_view()
做了什么。以UserView
为例
class View:
http_method_names = ["get", "post", "put", "patch", "delete", "head", "options", "trace"]
@classonlymethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs)
self.setup(request, *args, **kwargs)
return self.dispatch(request, *args, **kwargs)
return view
def dispatch(self, request, *args, **kwargs):
if request.method.lower() in self.http_method_names:
handler = getattr(
self, request.method.lower(), self.http_method_not_allowed
)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
def _allowed_methods(self):
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
as_view()
返回内部定义的view
函数。当有请求发过来时,会执行view
方法,创建UserView
实例对象,之后根据请求方式,来调用UserView
中定义的对应的方法。
dispatch
中主要逻辑很容易理解:
- 判断当前请求方式是否合法,合法的请求方式写在了
http_method_names
- 通过反射的方式
getattr()
,来获取UserView
实例中定义的相关方法。通过代码可以看到,定义的方法名必须是请求方式的小写。
2.2 APIView【源码有省略】
class APIView(View):
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
DRF在原先的View
基础上进行功能扩充:
request
重写
这一点体现在self.initialize_request(request, *args, **kwargs)
,如此得到的好处就是,可以更方便的获取前端传过来的参数。request.data
获取非GET请求的参数,request.query_parmas
获取GET请求参数
-
权限、许可、流量
这一点,体现在self.initial(request, *args, **kwargs)
-
对最终的response进行处理
这一点,体现在self.finalize_response(request, response, *args, **kwargs)
2.2.1 完整示例
from django.db import transaction
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.views import APIView
from blog.models import UserModel
# 序列化器, 之后会提
class UserSerializer(ModelSerializer):
class Meta:
model = UserModel
fields = '__all__'
class UserView(APIView):
def get(self, request):
user_list = UserModel.objects.all()
ser = UserSerializer(instance=user_list, many=True)
return Response(ser.data)
@transaction.atomic
def post(self, request):
ser = UserSerializer(data=request.data, many=False)
ser.is_valid(raise_exception=True)
ser.save()
return Response(ser.data)
class UserDetailView(APIView):
def get(self, request, id):
user = UserModel.objects.get(id=id)
ser = UserSerializer(instance=user, many=False)
return Response(ser.data)
@transaction.atomic
def put(self, request, id):
user = UserModel.objects.get(id=id)
ser = UserSerializer(instance=user, data=request.data)
ser.is_valid(raise_exception=True)
ser.save()
return Response(ser.data)
@transaction.atomic
def delete(self, request, id):
UserModel.objects.get(id=id).delete()
return Response('ok')
2.3 GenericAPIView
上面的实例中,可以看到接口中多次创建序列化器,可以再封装。
from django.db import transaction
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from blog.models import UserModel
class UserView(GenericAPIView):
# 具体能够设置什么变量, 建议看源码
queryset = UserModel.objects
serializer_class = UserSerializer
lookup_field = 'id'
def get(self, request):
user_list = self.get_queryset()
ser = self.get_serializer(instance=user_list, many=True)
return Response(ser.data)
@transaction.atomic
def post(self, request):
ser = self.get_serializer(data=request.data, many=False)
ser.is_valid(raise_exception=True)
ser.save()
return Response(ser.data)
class UserDetailView(GenericAPIView):
# 具体能够设置什么变量, 建议看源码
queryset = UserModel.objects
serializer_class = UserSerializer
lookup_field = 'id'
def get(self, request, id):
user = self.get_object()
ser = self.get_serializer(instance=user, many=False)
return Response(ser.data)
@transaction.atomic
def put(self, request, id):
user = self.get_object()
ser = self.get_serializer(instance=user, data=request.data)
ser.is_valid(raise_exception=True)
ser.save()
return Response(ser.data)
@transaction.atomic
def delete(self, request, id):
self.get_object().delete()
return Response('ok')
从这一步看来,其实并未有明显的代码量的下降。真正实现代码量的减少,是通过继承一些已经封装好的接口实现类xxxModelMixin
。
类 | 说明 |
---|---|
ListModelMixin | 里面有个list方法,查询某个表的全部数据,可分页 |
CreateModelMixin | 里面有个create方法,插入数据 |
RetrieveModelMixin | 里面有个retrieve方法,根据条件查询数据 |
UpdateModelMixin | 里面有个update方法,根据条件更新数据 |
DestroyModelMixin | 里面有个destory方法,根据条件删除数据 |
结合上面的内容,将例子再次简化,可得:
from rest_framework.mixins import (
CreateModelMixin,
DestroyModelMixin,
ListModelMixin,
RetrieveModelMixin,
UpdateModelMixin
)
class UserView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = UserModel.objects
serializer_class = UserSerializer
lookup_field = 'id'
def get(self, request):
return self.list(request)
@transaction.atomic
def post(self, request):
return self.create(request)
class UserDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = UserModel.objects
serializer_class = UserSerializer
lookup_field = 'id'
def get(self, request, id):
return self.retrieve(request)
@transaction.atomic
def put(self, request, id):
return self.update(request)
@transaction.atomic
def delete(self, request, id):
return self.destroy(request)
这体量就小了很多很多了。倘若你觉着继承这么多的类有些麻烦,也可以优化。
from rest_framework.generics import (
ListCreateAPIView,
RetrieveUpdateDestroyAPIView
)
class UserView(ListCreateAPIView):
queryset = UserModel.objects
serializer_class = UserSerializer
lookup_field = 'id'
class UserDetailView(RetrieveUpdateDestroyAPIView):
queryset = UserModel.objects
serializer_class = UserSerializer
lookup_field = 'id'
基本上不需要写什么代码了……
十分建议设置路由时,给参数进行命名。
# 推荐
re_path(r'^user/(?P<id>[\w\-]+)/$', UserDetailView.as_view())
# 不推荐
re_path(r'^user/([\w\-]+)/$', UserDetailView.as_view())
这里给出一条原因
在GenericAPIView
中有个get_object
方法
def get_object(self):
# 获取所有的数据
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
# lookup_field 默认值是'pk'
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
问题就出在断言上。kwargs
中参数是在dispatch
中设置的,只有当路径上参数是命名过的(比如推荐方式的路径命名),kwargs
中才会有值。倘若没有命名,那么得到的结果会保存在args
中,就get_object
的代码来看,它是不会到args
中找的。
2.4 ModelViewSet
一步步下来,可以看到接口是愈发简洁了。还能再简洁。将两个类合并成一个类。高度的封装。
# views.py
from rest_framework.viewsets import ModelViewSet
class UserViewSet(ModelViewSet):
queryset = UserModel.objects
serializer_class = UserSerializer
lookup_field = 'id'
# urls.py
from rest_framework.routers import DefaultRouter
from blog.views import UserViewSet
router = DefaultRouter()
router.register('user', viewset=UserViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
*router.urls
]