Django Rest Framework

1 安装

DRF官网

DRF仓库地址

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 写法

  1. 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
  1. 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中主要逻辑很容易理解:

  1. 判断当前请求方式是否合法,合法的请求方式写在了http_method_names
  2. 通过反射的方式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基础上进行功能扩充:

  1. request重写

这一点体现在self.initialize_request(request, *args, **kwargs),如此得到的好处就是,可以更方便的获取前端传过来的参数。request.data获取非GET请求的参数,request.query_parmas获取GET请求参数

  1. 权限、许可、流量
    这一点,体现在self.initial(request, *args, **kwargs)

  2. 对最终的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
]
posted @ 2022-11-26 23:52  silverbeats  阅读(43)  评论(0编辑  收藏  举报