美文网首页Drf/Django开发记录
Drf官网教程(一) - 序列化

Drf官网教程(一) - 序列化

作者: dyq666 | 来源:发表于2018-06-22 23:59 被阅读227次

    目录

    1. 环境搭建和项目初始化
    2. 创建Model和数据库迁移
    3. 创建Serializer
    4. 序列化与反序列化
    5. 使用ModelSerializer
    6. 完成View部分
    7. 完成Url部分
    8. 测试接口

    0. 概述

    本教程将完成一个pastebin web应用,通过应用对Drf进行综合的介绍,了解Drf中各个模块是如何组织在一起的。

    1. 环境搭建和项目初始化

    • 创建新的虚拟环境
    1. 安装虚拟环境管理包(win)
      pip install virtualenvwrapper-win
    2. 创建新的虚拟环境
      mkvirtualenv drf_tutorial
    • 安装Django相关的包

    安装pygments的原因是本教程是代码片段相关的web应用,需要这个包提供代码高亮功能。(建议安装ipython来增强Django的shell)
    pip install django==1.11 djangorestframework pygments ipython

    • 项目初始化
    1. 创建项目
      django-admin.py startproject drf_tutorial
    2. 创建应用
      python manage.py startapp snippets
    3. tutorial/settings.py配置INSTALLED_APPS
      django从比较新的版本开始(起码是1.11开始),都推荐使用下面这种方式注册来注册app。
    INSTALLED_APPS = [
        ...
        'rest_framework',
        'snippets.apps.SnippetsConfig'
    ]
    
    1. tutorial/settings.py修改语言和时区
    LANGUAGE_CODE = 'zh-hans'
    TIME_ZONE = 'Asia/Shanghai'
    USE_I18N = True
    USE_L10N = True
    USE_TZ = False
    

    2. 创建Model和数据库迁移

    • snippet/models.py中创建model
    1. 不用在意pygments相关的代码,不是本文重点(与Drf无关)。
    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)
        # 个人认为官网default与blank重复了,default其实也可以不用写,default默认值就是''。
        title = models.CharField(max_length=100, 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:
            # 通过创建时间获取record
            ordering = ('created',)
    
    • 数据库迁移

    python manage.py makemigrations snippets
    python manage.py migrate

    3. 创建Serializer

    • 概述

    序列化的操作与Django的Form类似。

    • 完成序列化代码

    snippets/创建serializers.py,在文件中创建类(Model名 + Serializer, 一种Serializer类名的命名习惯)

    from rest_framework import serializers
    
    from .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)
        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
    

    代码主要由两部分组成:

    1. 第一部分,由序列化/解序列化时的字段组成,字段一部分参数例如:required, max_length与Django-Form相同。而另一部属性比如code中的style是用于Drf的bBrowserAPI(后面章节会使用到)。
    2. 第二部分,override createupdate方法(这是必须的),在POST和PUT将调用serizlizer.save(),此时create和update会生效。
    3. create接受的是多个命名参数(每个参数都是model实例的一个字段),而validated_data是dict,所以对其进行了拆包。
    4. update会传入model的实例和序列化验证后的数据,由于部分字段不会被序列化检验(不在validated_data中),所以将实例中字段的值作为默认值。最后要返回被保存的实例。
    • 源码部分
    1. 如果不override createupdate方法,就会报错。下面是serializer.BaseSerializer的源码。
    2. 什么时候调用createupdate方法,下面是serializer.save的源码

      由源码可知,如果初始化时传递了instance就调用update,否则调用create
    • 后续

    这些代码看起来是没有必要(大部分代码都是固定的,比如override
    create方法),但是目前我们先保持这种清晰的声明方式,在之后将使用ModelSerializer减少代码量。

    4. 序列化与反序列化

    • 打开交互界面(建议安装ipython)

    python manage.py shell

    • 增加两个model实例
    from snippets.models import Snippet
    snippet = Snippet(code='foo = "bar"\n')
    snippet.save()
    
    snippet = Snippet(code='print "hello, world"\n')
    snippet.save()
    
    • 序列化
    1. 创建序列化实例
    from snippets.serializers import SnippetSerializer
    serializer = SnippetSerializer(snippet)
    serializer.data 
    # {'id': 4, 'title': '', 'code': 'print"hello, world"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
    type(serializer.data)
    # rest_framework.utils.serializer_helpers.ReturnDict
    

    序列化的数据类型。

    源码
    从上述测试以及源码可知,序列化实例中存放的数据仍是dict(仍是Python原生的数据结构),也就是说序列化仅仅完成了筛选,返回了一个筛选过后的dict,下一步使用哪种数据格式(Json还是Xml或者其他类型。)进行渲染与序列化无关。
    1. 使用Json数据格式
    from rest_framework.renderers import JSONRenderer
    content = JSONRenderer().render(serializer.data)
    content
    # b'{"id":4,"title":"","code":"print\\"hello, world\\"\\n","linenos":false,"language":"python","style":"friendly"}'
    type(content)
    # bytes
    

    经过Json化后dict变成了bytes,但是可以看到所有字符串都被双引号包裹,即Json的格式。

    • 反序列化

    bytes再变回dict

    1. 先将字节变成可操作的字节流
    2. six是Py2和Py3的兼容库。django将可兼容的工具放入了utils.six中。
    3. 解析后字节流(Json格式的)又变回了dict
    from rest_framework.parsers import JSONParser
    from django.utils.six import BytesIO
    stream = BytesIO(content)
    data = JSONParser().parser(stream)
    type(data) # dict
    data
    # {'id': 4,'title': '', 'code': 'print"hello, world"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
    
    • 转为序列化实例
    1. 通过从JSON中解析的data(类型是dict)构建一个序列化实例。
    2. 验证通过的情况下,使用.save()进行存储。
    serializer = SnippetSerializer(data=data)
    serializer.is_valid()
    # True
    serializer.validated_data
    # OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
    serializer.save()
    # <Snippet: Snippet object>
    
    • 序列化与反序列化的流程

    序列化与反序列化图
    • 序列化多个Model实例

    增加参数many=True

    serializer = SnippetSerializer(Snippet.objects.all(), many=True)
    serializer.data
    # [OrderedDict([...])...OrderedDict([...])]
    

    5. 使用ModelSerializer

    就像Django中Form有ModelForm一样,Serizlizer也有ModelSerilizer。

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

    ModelSerializer主要完成了两件事情:

    1. 将Model中的field根据元类中的fields指定的字段名转为序列的字段。(下面shell中的测试显示了自动转化的field的具体代码)
    2. 帮助我们实现了create和update方法(不用再重复的完成同样的工作)。
    from snippets.serializers import SnippetSerializer
    serializer = SnippetSerializer()
    print(repr(serializer))
    # SnippetSerializer():
    #    id = IntegerField(label='ID', read_only=True)
    #    title = CharField(allow_blank=True, max_length=100, required=False)
    #    code = CharField(style={'base_template': 'textarea.html'})
    #    linenos = BooleanField(required=False)
    #    language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
    #    style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...
    

    6. 完成View部分

    由于本章是序列化的章节,所以使用的是Django的view系统。

    • 完成list(GET)+create(POST)部分

    snippet/views.py

    from django.http import JsonResponse
    from django.views.decorators.csrf import csrf_exempt
    from rest_framework.parsers import JSONParser
    
    from snippets.models import Snippet
    from snippets.serializers import SnippetSerializer
    
    
    @csrf_exempt
    def snippet_list(request):
       """list & create"""
    
        # 获取所有snippets
        if request.method == 'GET':
            snippets = Snippet.objects.all()
            serializer = SnippetSerializer(snippets, many=True)
            return JsonResponse(serializer.data, safe=False)
    
        elif request.method == 'POST':
            # drf的request对django的request进行了封装,不需要使用request.POST
            # 1. JSON -> dict
            # 2. 序列化(form功能)
            data = JSONParser().parse(request)
            serializer = SnippetSerializer(data=data)
    
            # 1. 如果验证成功,就存储数据,并返回数据+201(创建成功)
            # 2. 验证失败,返回错误信息+400(语义有误)
            if serializer.is_valid():
                serializer.save()
                return JsonResponse(serializer.data, status=201)
            return JsonResponse(serializer.errors, status=400)
    
    1. 先使用装饰器(@csrf_exempt)取消防范csrf,因为前后端的web应用不再使用防范csrf。
    2. request在Drf中被封装了,增加了更多丰富的功能,比如不需要使用request.POST传递数据.
    3. JsonResponse中只要第一个参数传递的不是dict,就需要增加参数safe=False。(具体原因可看源码)
    • 完成retrieve,update,delete部分
    1. 状态码204代表成功但不会返回数据
    from django.http import HttpResponse
    
    
    @csrf_exempt
    def snippet_detail(request, pk):
        """retrieve, update, delete"""
    
        # 获取对应id的snippet,没有就返回404(没找到)
        try:
            snippet = Snippet.objects.get(pk=pk)
        except Snippet.DoesNotExist:
            return HttpResponse(status=404)
    
        if request.method == 'GET':
            # retrieve
            serializer = SnippetSerializer(snippet)
            return JsonResponse(serializer.data, status=200)
    
        elif request.method == 'PUT':
            # update
            data = JSONParser().parse(request)
            serializer = SnippetSerializer(snippet, data=data)
            if serializer.is_valid():
                serializer.save()
                return JsonResponse(serializer.data, status=200)
            return JsonResponse(serializer.errors, status=400)
    
        elif request.method == 'DELETE':
            snippet.delete()
            # 204不返回内容
            return HttpResponse(status=204)
    
    • 总结五种方法的操作流程


    • 未完善的地方

    没有考虑传入错误的Json格式或者访问了view无法处理的HTTP-METHOD,在这些情况实际上应该返回status-500("server error")。

    7. 完成Url部分

    这里的正则表达式的书写参照django中默认生成的admin的url配置方式:

    1. 在项目级的urls.py中,采用url(r'^应用名/', include('应用名.urls'))
    2. 应用的urls.py中,采用url(r'^$')配置listcreate,采用url(r'^(?P<pk>[0-9]+)/$')配置retrive,putdelete
    • 完成snippets/urls.py部分
    from django.conf.urls import url
    from snippets import views
    
    urlpatterns = [
        url(r'^$', views.snippet_list),
        url(r'^(?P<pk>[0-9]+)/$', views.snippet_detail)
    ]
    
    • 完成drf_tutorial/urls.py部分
    from django.conf.urls import include
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls), # 默认生成的
        url(r'^snippets/', include('snippets.urls'))
    ]
    

    8. 测试接口

    1. 运行python manage.py runserver
    2. 主要测试下面两个接口:
      1. http://127.0.0.1:8000/snippets/
      2. http://127.0.0.1:8000/snippets/2/(测试这个Url若有正则匹配报错需仔细检查url中配置是否错误)
    3. 可有两种供选择的测试方式:
      1. 在浏览器中测试
        需要安装JsonView来更好的观察Json数据(Chrome)。
      2. 在终端中测试
        pip install httpie
        直接在终端中输入http 待测试的url

    相关文章

      网友评论

      • 可爱的煎饼:官网跟这里不太一样
        在反序列化部分import是JSONParser
        from rest_framework.parsers import JSONParsers
        data = JSONParser().parser(stream)应该是用parse这个函数
        dyq666:感谢,确实多打了个's', 已更正!
      • 邓老墨:怒赞

      本文标题:Drf官网教程(一) - 序列化

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