您现在的位置是:网站首页 > 分类 > 文章详情

djangorestframework细究

T2018年9月28日 14:20594人围观
简介在学习过django的基础上,我们再来一起学习下前后端分离框架:djangorestframework

本篇就不讲细节了,主要也就记录下学习过程,如有不明白请翻到最后直接下载代码看
drf开发的过程主要分为:创建工程-配置settings-models-serialization-views/viewsets-urls&router,主要的功能模块就是serialization、viewsets
中间调试我们使用swagger,所以也需要引入swagger:安装swagger->引入到工程->settings加入swagger配置->urls配置,下面就主要来一起看下重点部分serialization和viewsets

Serialization:

先来看个图,我们的请求处理过程:
上图解释下:post和patch过来的方法,就会进入到serializer中的create/update方法中进行数据处理存储

好了,下面看下代码

第一种方法,我们需要自己去定义请求/响应的字段以及类型,并且必须实现create和update方法

class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    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')

    def create(self, validated_data):
        return Snippet.objects.create(**validated_data)

    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

第二种方法,继承ModelSerializer,我们只需要在元类中定义model、field即可,无须实现create/update方法,当然如果需要你也可以去重写

class SnippetSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username') 

    class Meta:
        model = Snippet  # 选取模型进行序列化
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style', 'owner')  # 模型中需要返回的字段
        # fields = "__all__"

对上面的fields作个说明:
1)这个fields就是响应给用户的字段值,或则说是用户创建或更新请求过来需要的入参是哪些,当然这些字段必须是模型中存在的
2)如果模型中不存在的字段,但是你又想响应给用户,那么就使用SerializerMethodField方法自定义
3)还有几个常用的关联:StringRelatedField、PrimaryKeyRelatedField、SlugRelatedField、内嵌(外键)
4) 可以重新定义model中存在的字段,主要是添加一些校验,比如UniqueValidator,抑或标记字段为write_only/read_only等,如下

class UserRegisterSerializer(serializers.ModelSerializer):

    username = serializers.CharField(label="用户名", help_text="用户名", required=True, allow_blank=False,validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")])  # UniqueValidator

    password = serializers.CharField(style={'input_type': 'password'}, help_text="密码", label="密码", write_only=True) # write_only

    class Meta:
        model = User
        fields = ('username', 'password')

5)或许你对于第四点有些疑问,不在model中的字段我们就没法定义入参了吗,比如修改密码需要一个新密码newpassword,肯定是需要成为入参的,必须定义啊,那怎么办,办法就是重写下validate方法(也就是把model中没有的newpassword这个字段在validte方法中不返回,那么最后调用perform_create也就不会因为将不在model中的字段执行保存进数据库而报错了)

6)了解下其他验证方式,如UniqueTogetherValidator 。还有一个CurrentUserDefault,也比较好用可以看看

    # 有时候前端不需要传一个或多个字段,这些字段值是直接根据用户登录信息判断自动赋值的
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())

    class Meta:
        model = UserFav
        validators = [
            UniqueTogetherValidator(
                queryset=UserFav.objects.all(),  # 用于明确验证唯一性集合,必须设置
                fields=('articles', 'user'),  # 列表或元组(哪些需要做唯一性校验的字段),字段必须是序列化类中存在的字段
                message="已经收藏"
            )
        ]
        # fields = "__all__"
        fields = ('user', 'articles', 'id')

viewsets:

viewsets主要涉及token/session等授权、api权限控制、分页、查询(排序)
还有get_serializer_class/get_permissions/get_queryset/get_object/create等方法的重写,后续再补,那就先这样了
2018.9.29 新的一天又开始了,我们继续来学习下剩下viewsets

那么先来看下view中的几种写法,也就是view-apiview-genericapiview-viewset的进化过程,目前我们推荐使用viewset方法最为简单

第一种方法(没有使用drf框架的requests/responses/@api_view/状态码)

@csrf_exempt
def snippet_list(request):
    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)  # post的数据是json格式的,由于后续需要调用SnippetSerializer,因此必须要反序列化一下
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)


第二种方法(使用了request/response/status,必须加上@api_view装饰才可以用,因为@api_view是让请求经过rest框架处理)

@api_view(['GET', 'POST'])
def snippet_list(request, format=None):
    if request.method == 'GET':
            snippets = Snippet.objects.all()
            serializer = SnippetSerializer(snippets, many=True)
            return Response(serializer.data)
    elif request.method == 'POST':
            # data = JSONParser().parse(request)  # 使用这种方法不需要将请求的数据格式转换成可序列化的类型
            serializer = SnippetSerializer(data=request.data)  # 直接将之前的data=data改成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 based views(换作了class来定义,主要是让代码实现复用) ,同时定义了一些权限、节流等新方法

class SnippetList(APIView):
    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)


第四种方法,混合使用GenericAPIView和mixins,引入了queryset 、serializer_class、lookup_field、pagination_class、filter_backend,并提供了get_object,get_queryset方法,非常方便使用

class SnippetList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    # 可以不用去实现,因为mixin中已经写过了,当然有需要你可以去重载
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
    # 同get一样
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)


这里也就趁机一起来看看get_permissions/get_queryset/get_object/create
1)get_permissions,通过三种方式可以去定义它(全局定义/view中定义permission_classes/view中重写permission_classes方法)

    # 这个是定义permission_classes(当然这个不能与下面重写共存的,不然这个permission_classes就失效了),这里是一个元组,其中IsOwnerOrReadOnly是自定义的权限处理,有特殊需求下自己可以写个类去处理  
    permission_classes = (permissions.IsAuthenticated, IsOwnerOrReadOnly)
    # 这个是重写permission_classes
    def get_permissions(self):
        if self.action == "retrieve":
            return [permissions.IsAuthenticated()]
        elif self.action == "create":
            return []

        return []

2)再来一起看下get_queryset,这个呢,主要就是用来定义我们的查询集,最终响应给用户,我们可以直接定义queryset = User.objects.all(),或则通过重写

    def get_queryset(self):
        if self.request.user and self.request.user.is_authenticated:
            queryset = UserFav.objects.filter(user=self.request.user)
        else:
            queryset = []
        return queryset

3)get_object,就是通过我们view中定义的lookup_field去过滤下我们的查询集query_set,然后返回这个对象,另外需要知道的是retrive/update/destory中可通过instance = self.get_object()获取对象,比如吧,retrive这个我们在访问文章后增加阅读量时候经常需要重写

    # 重写下,浏览数+1
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()  # 获取article这个对象
        instance.read_num += 1  # 将article中的read_num字段值加1
        instance.save()
        serializer = self.get_serializer(instance)  # 找到article的序列化类进行序列化
        return Response(serializer.data)

4)create/update/retrive等这一系列就不说了,其实前面也说到了retrive的重写,其他也类似,如果让drf框架去处理的话基本就是将你请求的入参进行一系列处理保存进数据库,其他的特殊需要你必须重写

还有什么没有讲呢,对,还有lookup_field、pagination_class、filter_backend,接着一起看看吧

5)lookup_field,默认是通过id查询,在get_object中会使用到这个值,说白了就是像retrive这样的请求时,会通过lookup_field的值去过滤query_set
6)pagination_class,这个就更简单了,配置下就可以用了,也不需要特别的处理,基本够用了,复杂的也没研究,看下面

class ArticlePagination(PageNumberPagination):
    page_size = 5  # 默认每页显示几条,也可以在接口中传入page_size自定义每页显示的数量
    page_size_query_param = 'page_size'  # 前端发送的每页数目关键字名,默认为None
    page_query_param = "page"  # 前端发送的页数关键字名,默认为"page"
    max_page_size = 100  # 前端最多能设置的每页数量
    # limit_query_param = 1  # 设置传入条数,目前还不知道什么用

    pagination_class = ArticlePagination

出来的效果看看,打开swagger,现身吧
7)filter_backend也比较简单,view中定义一个filter_backends,包括三种搜索方式filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter),后面两种SearchFilter和OrderingFilter比较简单,我们先讲

    filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
    # 首先filter_backends中配上DjangoFilterBackend
    filter_class = ArticleFilter
    # 首先filter_backends中配上filters.SearchFilter,另外查询外键关联的字段,使用item__title这样的方式
    search_fields = ('title', 'item__title', 'tags__name')  # 这个就是搜索时候可根据这么几个字段值去搜索
    # 首先filter_backends中配上filters.OrderingFilter
    ordering_fields = ('id', 'publish_date')  # 这也也类似searh_fields,排序时可根据这么几个值去排

这个最终出来的效果,我们看个图可能更加清晰,打开swagger,来来来

前面代码中提到的,还有这个filter_class = ArticleFilter我们尚未说,这个是啥,就是自定义的过滤,看下面这段代码,理解下

import django_filters
from .models import Article
from rest_framework import filters


# 自定义搜索过滤器
class ArticleFilter(django_filters.rest_framework.FilterSet):
    # 注释掉也是可以的,只要Article model中有对应字段
    # author = django_filters.CharFilter(help_text="作者")
    # status = django_filters.CharFilter(help_text="状态")  # 从ChoiceFilter改成了CharFilter
    # publish_date = django_filters.DateTimeFilter(help_text="发布时间")
    # item = django_filters.CharFilter(help_text="分类")
    # tags = django_filters.CharFilter(help_text="标签")
    # is_active = django_filters.CharFilter(help_text="是否热门")
    # model中没有的字段,可以这么定义使用
    categorys = django_filters.NumberFilter(method='item_categorys_filter', help_text="大类")

    # 这里4个入参是必须的(根据外键item去查询大类:item__categorys)
    def item_categorys_filter(self, queryset, name, value):
        return queryset.filter(item__categorys=value)

    class Meta:
        model = Article
        fields = ['author', 'status', 'publish_date', 'is_active', 'item', 'categorys', 'tags']

出来的效果,打开swagger看看,一目了然了

对上图说明下,model中对应的外键,我们查询的话都是默认使用id去查询的,比如这里的外键author/item/categorys/tags

第五种终极写法viewset,其实跟第四种基本一样的

class SnippetViewSet(viewsets.ModelViewSet):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

那为什么会衍生出这种方法呢,请看大屏幕:
1)GenericViewSet重写了as_view方法,可以获取到HTTP的请求方法(也就是你可以去重写get_serializer_class使不同的请求走不同的序列化方法)

    # 重写返回的序列化类
    def get_serializer_class(self):
        if self.action == "retrieve":
            return UserDetailSerializer
        elif self.action == "create":
            return UserRegisterSerializer

        return UserDetailSerializer

2)url的绑定方法边得更加简单
不使用viewset,也不使用class定义我们的view时,我们是这么写的(对应前面说的第一、二中方法)

urlpatterns = [
    url(r'^snippets/$', views.snippet_list),
    url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]

不使用viewset,但是使用class定义我们的view时,我们是这么写的(对应前面说的第三、四中方法)

urlpatterns = [
    url(r'^snippets/$', views.SnippetList.as_view(), name='snippet-list'),
    url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]

使用viewset,我们是这么写的(对应前面说的第五中方法)

router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)
urlpatterns = [
    url(r'^', include(router.urls))
]

本文参考:

my project in github:https://github.com/wuyajun2016/drflearn.git
drf home:http://www.django-rest-framework.org/tutorial/quickstart/
swagger:https://www.jianshu.com/p/dbb92ba83fb1

文章评论

阿里云-云大使推广
阿里云-云服务器推广

微信公众号