Drf/Django开发记录

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

2018-06-22  本文已影响227人  dyq666

目录

  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

安装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和数据库迁移

  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. 序列化与反序列化

python manage.py shell

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>
序列化与反序列化图

增加参数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系统。

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。(具体原因可看源码)
  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
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)
]
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
上一篇下一篇

猜你喜欢

热点阅读