Python的Django REST框架中的序列化及请求和返回


Posted in Python onApril 11, 2016

序列化Serialization
1. 设置一个新的环境

在我们开始之前, 我们首先使用virtualenv要创建一个新的虚拟环境,以使我们的配置和我们的其他项目配置彻底分开。

$mkdir ~/env

$virtualenv ~/env/tutorial

$source ~/env/tutorial/bin/avtivate

现在我们处在一个虚拟的环境中,开始安装我们的依赖包

$pip install django

$pip install djangorestframework

$pip install pygments  ////使用这个包,做代码高亮显示

需要退出虚拟环境时,运行deactivate。更多信息,irtualenv document

2. 开始

环境准备好只好,我们开始创建我们的项目

$ cd ~

$ django-admin.py startproject tutorial

$ cd tutorial

项目创建好后,我们再创建一个简单的app

$python manage.py startapp snippets

我们使用sqlite3来运行我们的项目tutorial,编辑tutorial/settings.py, 将数据库的默认引擎engine改为sqlite3, 数据库的名字NAME改为tmp.db

DATABASES = {
  'default': {
    'ENGINE': 'django.db.backends.sqlite3',
    'NAME': 'tmp.db',
    'USER': '',
    'PASSWORD': '',
    'HOST': '',
    'PORT': '',
  }
}

同时更改settings.py文件中的INSTALLD_APPS,添加我们的APP snippets和rest_framework

INSTALLED_APPS = (
  ...
  'rest_framework',
  'snippets',
)

在tutorial/urls.py中,将snippets app的url包含进来

urlpatterns = patterns('',
  url(r'^', include('snippets.urls')),
)

3. 创建Model
这里我们创建一个简单的nippets model,目的是用来存储代码片段。

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, 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',)

完成model时,记得sync下数据库

python manage.py syncdb

4. 创建序列化类

我们要使用我们的web api,要做的第一件事就是序列化和反序列化, 以便snippets实例能转换为可表述的内容,例如json. 我们声明一个可有效工作的串行器serializer。在snippets目录下面,该串行器与django 的表单形式很类似。创建一个serializers.py ,并将下面内容拷贝到文件中。

from django.forms import widgets
from rest_framework import serializers
from snippets.models import Snippet

class SnippetSerializer(serializers.Serializer):
  pk = serializers.Field() # Note: `Field` is an untyped read-only field.
  title = serializers.CharField(required=False,
                 max_length=100)
  code = serializers.CharField(widget=widgets.Textarea,
                 max_length=100000)
  linenos = serializers.BooleanField(required=False)
  language = serializers.ChoiceField(choices=models.LANGUAGE_CHOICES,
                    default='python')
  style = serializers.ChoiceField(choices=models.STYLE_CHOICES,
                  default='friendly')

  def restore_object(self, attrs, instance=None):
    """
    Create or update a new snippet instance.
    """
    if instance:
      # Update existing instance
      instance.title = attrs['title']
      instance.code = attrs['code']
      instance.linenos = attrs['linenos']
      instance.language = attrs['language']
      instance.style = attrs['style']
      return instance

    # Create new instance
    return Snippet(**attrs)

该序列化类的前面部分,定义了要序列化和反序列化的类型,restore_object 方法定义了如何通过反序列化数据,生成正确的对象实例。

我们也可以使用ModelSerializer来快速生成,后面我们将节省如何使用它。
5. 使用 Serializers

在我们使用我们定义的SnippetsSerializers之前,我们先熟悉下Snippets.

$python manage.py shell

进入shell终端后,输入以下代码:

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser

snippet = Snippet(code='print "hello, world"\n')
snippet.save()

我们现在获得了一个Snippets的实例,现在我们对他进行以下序列化

serializer = SnippetSerializer(snippet)
serializer.data
# {'pk': 1, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}

这时,我们将该实例转成了python原生的数据类型。下面我们将该数据转换成json格式,以完成序列化:

content = JSONRenderer().render(serializer.data)
content
# '{"pk": 1, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'

反序列化也很简单,首先我们要将一个输入流(content),转换成python的原生数据类型

import StringIO

stream = StringIO.StringIO(content)
data = JSONParser().parse(stream)

然后我们将该原生数据类型,转换成对象实例

serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.object
# <Snippet: Snippet object>

注意这些API和django表单的相似处。这些相似点, 在我们讲述在view中使用serializers时将更加明显。
6. 使用 ModelSerializers

SnippetSerializer使用了许多和Snippet中相同的代码。如果我们能把这部分代码去掉,看上去将更佳简洁。

类似与django提供Form类和ModelForm类,Rest Framework也包含了Serializer 类和 ModelSerializer类。

打开snippets/serializers.py ,修改SnippetSerializer类:

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

7. 通过Serializer编写Django View

让我们来看一下,如何通过我们创建的serializer类编写django view。这里我们不使用rest framework的其他特性,仅编写正常的django view。

我们创建一个HttpResponse 子类,这样我们可以将我们返回的任何数据转换成json。

在snippet/views.py中添加以下内容:

from django.http import HttpResponse
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

class JSONResponse(HttpResponse):
  """
  An HttpResponse that renders it's content into JSON.
  """
  def __init__(self, data, **kwargs):
    content = JSONRenderer().render(data)
    kwargs['content_type'] = 'application/json'
    super(JSONResponse, self).__init__(content, **kwargs)

我们API的目的是,可以通过view来列举全部的Snippet的内容,或者创建一个新的snippet

@csrf_exempt
def snippet_list(request):
  """
  List all code snippets, or create a new snippet.
  """
  if request.method == 'GET':
    snippets = Snippet.objects.all()
    serializer = SnippetSerializer(snippets)
    return JSONResponse(serializer.data)

  elif request.method == 'POST':
    data = JSONParser().parse(request)
    serializer = SnippetSerializer(data=data)
    if serializer.is_valid():
      serializer.save()
      return JSONResponse(serializer.data, status=201)
    else:
      return JSONResponse(serializer.errors, status=400)

注意,因为我们要通过client向该view post一个请求,所以我们要将该view 标注为csrf_exempt, 以说明不是一个CSRF事件。
Note that because we want to be able to POST to this view from clients that won't have a CSRF token we need to mark the view as csrf_exempt. This isn't something that you'd normally want to do, and REST framework views actually use more sensible behavior than this, but it'll do for our purposes right now.
我们也需要一个view来操作一个单独的Snippet,以便能更新/删除该对象。

@csrf_exempt
def snippet_detail(request, pk):
  """
  Retrieve, update or delete a code snippet.
  """
  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)
    else:
      return JSONResponse(serializer.errors, status=400)

  elif request.method == 'DELETE':
    snippet.delete()
    return HttpResponse(status=204)

将views.py保存,在Snippets目录下面创建urls.py,添加以下内容:

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

注意我们有些边缘事件没有处理,服务器可能会抛出500异常。
8. 测试

现在我们启动server来测试我们的Snippet。

在python mange.py shell终端下执行(如果前面进入还没有退出)

>>quit()

执行下面的命令, 运行我们的server:

python manage.py runserver

Validating models...

0 errors found
Django version 1.4.3, using settings 'tutorial.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

新开一个terminal来测试我们的server

序列化:

url http://127.0.0.1:8000/snippets/

[{"id": 1, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}]
 url http://127.0.0.1:8000/snippets/1/

{"id": 1, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}

Request and Response
1. Request Object  ——Request对象

rest framework 引入了一个继承自HttpRequest的Request对象,该对象提供了对请求的更灵活解析。request对象的核心部分是request.data属性,类似于request.post, 但在使用WEB API时,request.data更有效。

(1)request.POST  # Only handles form data.  Only works for 'POST' method.
(2)request.DATA  # Handles arbitrary data.  Works any HTTP request with content.
2. Response Object ——Response对象
rest framework引入了一个Response 对象,它继承自TemplateResponse对象。它获得未渲染的内容并通过内容协商content negotiation 来决定正确的content type返回给client。

return Response(data)  # Renders to content type as requested by the client.
3. Status Codes
在views当中使用数字化的HTTP状态码,会使你的代码不宜阅读,且不容易发现代码中的错误。rest framework为每个状态码提供了更明确的标识。例如HTTP_400_BAD_REQUEST。相比于使用数字,在整个views中使用这类标识符将更好。
4. 封装API views

在编写API views时,REST Framework提供了两种wrappers:

1). @api_viwe 装饰器 ——函数级别

2). APIView 类——类级别

这两种封装器提供了许多功能,例如,确保在view当中能够接收到Request实例;往Response中增加内容以便内容协商content negotiation 机制能够执行。

封装器也提供一些行为,例如在适当的时候返回405 Methord Not Allowed响应;在访问多类型的输入request.DATA时,处理任何的ParseError异常。
5. 汇总

我们开始用这些新的组件来写一些views。

我们不在需要JESONResponse 类(在前一篇中创建),将它删除。删除后我们开始稍微重构下我们的view

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):
  """
  List all snippets, or create a new snippet.
  """
  if request.method == 'GET':
    snippets = Snippet.objects.all()
    serializer = SnippetSerializer(snippets)
    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)
    else:
      return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

上面的代码是对我们之前代码的改进。看上去更简洁,也更类似于django的forms api形式。我们也采用了状态码,使返回值更加明确。
下面是对单个snippet操作的view更新:

@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)
    else:
      return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

  elif request.method == 'DELETE':
    snippet.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)

注意,我们并没有明确的要求requests或者responses给出content type。request.DATA可以处理输入的json请求,也可以输入yaml和其他格式。类似的在response返回数据时,REST Framework返回正确的content type给client。

6. 给URLs增加可选的格式后缀

利用在response时不需要指定content type这一事实,我们在API端增加格式的后缀。使用格式后缀,可以明确的指出使用某种格式,意味着我们的API可以处理类似http://example.com/api/items/4.json.的URL。

增加format参数在views中,如:

def snippet_list(request, format=None):
and

def snippet_detail(request, pk, format=None):

现在稍微改动urls.py文件,在现有的URLs中添加一个格式后缀pattterns (format_suffix_patterns):

from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns

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

urlpatterns = format_suffix_patterns(urlpatterns)

这些额外的url patterns并不是必须的。

Python 相关文章推荐
简单了解Python中的几种函数
Nov 03 Python
Python实现求解一元二次方程的方法示例
Jun 20 Python
Linux系统(CentOS)下python2.7.10安装
Sep 26 Python
python判断输入日期为第几天的实例
Nov 13 Python
python实现朴素贝叶斯算法
Nov 19 Python
DES加密解密算法之python实现版(图文并茂)
Dec 06 Python
Python3爬楼梯算法示例
Mar 04 Python
python cv2读取rtsp实时码流按时生成连续视频文件方式
Dec 25 Python
Python chardet库识别编码原理解析
Feb 18 Python
在Keras中实现保存和加载权重及模型结构
Jun 15 Python
python实现局部图像放大
Nov 17 Python
Python中的 No Module named ***问题及解决
Jul 23 Python
python中的错误处理
Apr 10 #Python
python脚本实现xls(xlsx)转成csv
Apr 10 #Python
Python使用gensim计算文档相似性
Apr 10 #Python
Python调用SQLPlus来操作和解析Oracle数据库的方法
Apr 09 #Python
python调用fortran模块
Apr 08 #Python
python3使用urllib模块制作网络爬虫
Apr 08 #Python
Python抓取电影天堂电影信息的代码
Apr 07 #Python
You might like
从零开始的异世界生活:第二季延期后,B站上架了第二部剧场版
2020/05/06 日漫
通过php添加xml文档内容的方法
2015/01/23 PHP
ThinkPHP框架安全实现分析
2016/03/14 PHP
php 实现简单的登录功能示例【基于thinkPHP框架】
2019/12/02 PHP
Laravel框架Blade模板简介及模板继承用法分析
2019/12/03 PHP
JS返回上一页实例代码通过图片和按钮分别实现
2013/08/16 Javascript
ie7+背景透明文字不透明超级简单的实现方法
2014/01/17 Javascript
jQuery遍历Table应用示例
2014/04/09 Javascript
多种JQuery循环滚动文字图片效果代码
2020/06/23 Javascript
手机端点击图片放大特效PhotoSwipe.js插件实现
2016/08/24 Javascript
JS 对java返回的json格式的数据处理方法
2016/12/05 Javascript
javascript 判断当前浏览器版本并判断ie版本
2017/02/17 Javascript
jQuery日程管理控件glDatePicker用法详解
2017/03/29 jQuery
简单谈谈原生js的math对象
2017/06/27 Javascript
react-native 完整实现登录功能的示例代码
2017/09/11 Javascript
webpack+vue-cil中proxyTable处理跨域的方法
2018/07/20 Javascript
又拍云 Node.js 实现文件上传、删除功能
2018/10/28 Javascript
Windows上node.js的多版本管理工具用法实例分析
2019/11/06 Javascript
小程序实现列表展开收起效果
2020/07/29 Javascript
Python内置函数Type()函数一个有趣的用法
2015/02/18 Python
通过数据库对Django进行删除字段和删除模型的操作
2015/07/21 Python
python xml.etree.ElementTree遍历xml所有节点实例详解
2016/12/04 Python
TensorFlow使用Graph的基本操作的实现
2020/04/22 Python
Python实现密钥密码(加解密)实例详解
2020/04/26 Python
Python排序函数的使用方法详解
2020/12/11 Python
Nike比利时官网:Nike.com (BE)
2019/02/07 全球购物
营业员演讲稿
2013/12/30 职场文书
中学生校园广播稿
2014/01/16 职场文书
开学典礼演讲稿
2014/05/23 职场文书
法人授权委托书
2014/09/16 职场文书
三严三实学习心得体会
2014/10/13 职场文书
社区公民道德宣传日活动总结
2015/03/23 职场文书
中国合伙人观后感
2015/06/02 职场文书
高三语文教学反思
2016/02/16 职场文书
2016年安全生产先进个人事迹材料
2016/02/29 职场文书
MySQL中存储时间的最佳实践指南
2021/07/01 MySQL