美文网首页django-rest-framework开发
django-rest-framework(实战篇)——tuto

django-rest-framework(实战篇)——tuto

作者: Ccccolin_aha | 来源:发表于2018-04-10 01:11 被阅读24次

    一、序列化

    创建模型类:

    创建一个Snippet模型类,用于储存代码段,编写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())  # 得到所有的配色风格
    
    
    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',)
    

    迁移到数据库:

    python manage.py makemigrations snippets
    python manage.py migrate
    

    创建序列化类

    首先解释一下序列化:在这里可以先简单的理解为serializer把模型实例转化为json格式然后响应出去,这样便于客户端调用时解析使用。

    那么反序列化其实道理差不多,反序列化之后的数据格式更便于后台使用。

    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)
        # 利用字段标志控制序列化器渲染到HTML页面时的的显示模板
        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')
    
        # 给定经过验证的数据,创建并返回一个新的 Snippet 实例
        def create(self, validated_data):
            return Snippet.objects.create(**validated_data)
    
        # 给定经过验证的数据,更新并返回一个已经存在的 Snippet 实例
        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类中有很多代码其实是和models.py中的Snippet模型类似一样的,所以这里我们可以改进一下。就像在Django中提供了Form类和ModelForm类一样,django-rest-framework为我们提供了Serializer类和ModelSerializer类。利用它可以让我们的代码简洁很多,修改serializers.py:

    class SnippetSerializer(serializers.ModelSerializer):
        class Meta:
            model = Snippet
            fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
    

    编写常规的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):
        """
        列出所有已经存在的snippet或者创建一个新的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):
        """
        检索查看、更新或者删除一个代码段
        """
        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:
    先创建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')),
    ]
    

    二、请求和响应

    精简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):
        """
        列出所有已经存在的snippet或者创建一个新的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)
    

    和上一个版本对比有如下不同:

    • Request对象:

    平时我们在写Django的视图函数的时候,都会带上一个request参数,这样就能处理平时搭建网站时,浏览器访问网页时发出的常规的HttpRequest。但是现在我们导入了django-rest-framework,它能够对request进行拓展,并且提供更灵活的请求解析。这个特性体现在哪呢?请看下面这个例子:

    request.POST  # 只能处理表单数据.只能处理POST请求
    request.data  # 能处理各种数据。  可以处理'POST', 'PUT' 和 'PATCH'模式的请求
    

    拓展后的request使用request.data就可以处理各种各样的请求了,而原本的request在处理时需要指定请求模式。

    • Response对象:

    和request对象一样,django-rest-framework也对其进行了很实用的拓展,在上一个版本中,我们导入了JsonResponse用于返回json格式的响应。

    也就是说,在return的时候就需要指明json格式,这样显得很不实用而且很单一,所以经过拓展后的Reponse对象就很方便了,它会根据客户端的请求头部信息来确定正确的内容类型以返回给客户端。只需如下代码:

    return Response(data)
    
    • 状态码

    • 装饰API视图

    三、基于类的视图CBV(Class Basic View)

    这里要做的是把基于方法的视图改为基于类的视图,我们将会了解到APIView
    重构一下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):
        """
        列出所有已经存在的snippet或者创建一个新的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)
    

    和原来的相比,可以发现基于类的视图把各种不同的HTTP请求分离开变成单个的方法,而不是if...elif...这样的结构,所以这样处理起来很更加的高效。

    class SnippetDetail(APIView):
        """
        检索查看、更新或者删除一个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)
    

    改为基于类的视图之后,修改一下路由了,对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()方法,当然,使用这两个函数需要先重载GenericAPIView中的queryset和serializer_class属性,这点我们查看一下mixins的源码就可以看出来了。比如list方法会分别通过get_queryset()和get_serializer()得到查询集和序列化器,其他封装好的方法也是如此。

    修改另外一个视图:

    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)
    

    使用通用的视图类

    进一步精简代码:

    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只是对上一个版本代码的简单封装。

    四、ViewSets和Routers

    在第一部分和第二部分中写的编写Django视图时,使用的都是基于函数的方法,并且每个视图函数之前都会加一个django-rest-framework带的装饰器@api_view。然后在第三部分,我们就开始把基于函数的视图改成了基于类的视图,然后发现这样做视图部分减少了很多代码量。

    使用ViewSet重构视图

    拿本项目为例子,我们之前查看所有snippet列表就要写一个视图类SnippetList,并在urls.py中为其设置一个模式然后as_view使用它,然后要看单个用户的详情页就要再写一个SnippetDetail视图类并再在添加一个url模式。同时注意到这两个视图类都是继承的generics.XXXAPIView。而使用ViewSets我们就可以把SnippetList和SnippetDetail合并成SnippetViewSet视图类,并且继承的类改为viewsets.ModelViewSet,这样就是一个视图集了。

    class SnippetViewSet(viewsets.ModelViewSet):
        """
        viewset自动提供了`list`, `create`, `retrieve`,  `update` 和 `destroy` 动作.
        """
        queryset = Snippet.objects.all()
        serializer_class = SnippetSerializer
    

    将ViewSets明确的绑定到URL

    每个视图集的url模式都需要我们在as_view中传入参数,把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)),
    ]
    

    五、权限和认证

    Object

    • snippets 与User相关联
    • 只有登录的用户才可以创建snippets
    • 只有创建该snippets的用户有编辑权限
    • 未登录的用户只有访问的权限

    修改Snippets Model

    在Snippet模型添加一个owner字段作为外键

    owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
    

    添加UserSerializer

    在snippets/serializers.py中添加一个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字段的数据,而snippet在User模型中是一个反向关系,User这边查询不到用户创建的snippet,所以我们需要手动为UserSerializer添加snippets字段。

    添加相应的UserView

    class UserList(generics.ListAPIView):
        queryset = User.objects.all()
        serializer_class = UserSerializer
    
    
    class UserDetail(generics.RetrieveAPIView):
        queryset = User.objects.all()
        serializer_class = UserSerializer
    

    路由分发

    url(r'^users/$', views.UserList.as_view()),
    url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
    

    关联Snippets和Users

    如果像之前那样创建代码段的话,我们还不能把Snippets和Users关联起来。因为在使用的时候User的数据是通过request传入的,而不是以序列化的数据传递过来。

    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)
    

    这个perform_create() 可以让用户在通过POST请求创建一个新的Snippet时,会把request中的user赋值给Snippet的owner。

    为了提高可读性,在API中要让owner显示用户名,所以我们在SnippetSerializer 下面增加一个字段:

        owner = serializers.CharField(read_only=True, source='owner.username')
    

    source参数指明显示owner模型的哪一个字段

    添加权限

    现在Snippet和User已经关联起来并且是可浏览的。接下来实现权限判断:

    from rest_framework import permissions
    
    # 在SnippetList和SnippetDetail类中加入
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
    

    功能:验证当前用户是否Login,若是,则可以创建Snippet,编辑(包括修改、删除)某一个具体的Snippet,若否,则只能浏览Snippet。

    添加对象权限

    接着我们要实现的是每个Snippet只有其创建者才可以对其进行更改、删除等操作。因此,我们需要设置一下自定义权限,使每个Snippet只允许其创建者编辑它。在snippets目录下新建一个permissions.py:

    from rest_framework import permissions
    
    
    class IsOwnerOrReadOnly(permissions.BasePermission):
        """
        使每个Snippet只允许其创建者编辑它
        """
    
        def has_object_permission(self, request, view, obj):
    
        # 任何用户或者游客都可以访问任何Snippet,所以当请求动作在安全范围内,
        # 也就是GET,HEAD,OPTIONS请求时,都会被允许
            if request.method in permissions.SAFE_METHODS:
                return True
    
        # 而当请求不是上面的安全模式的话,那就需要判断一下当前的用户
        # 如果Snippet所有者和当前的用户一致,那就允许,否则返回错误信息
            return obj.owner == request.user
    

    六、添加超链接

    相关文章

      网友评论

        本文标题:django-rest-framework(实战篇)——tuto

        本文链接:https://www.haomeiwen.com/subject/fvgwhftx.html