Django Rest Framework(第一天)—django-rest-framework快速入门

一、django-rest-framework快速入门

Django REST framework是一个基于Django实现的一个restful框架,一个十分强大切灵活的工具包,用以构建Web APIs。

Djando REST framework的优点:

在线可视的API 验证策略涵盖了OAuth1和OAuth2 同时支持ORM和非ORM数据源的序列化 支持基于类的视图

REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。其中写道:

本文研究计算机科学两大前沿----软件和网络----的交叉点。长期以来,软件研究主要关注软件设计的分类、设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响。而相反地,网络研究主要关注系统之间通信行为的细节、如何改进特定通信机制的表现,常常忽视了一个事实,那就是改变应用程序的互动风格比改变互动协议,对整体表现有更大的影响。我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。

1.1 REST原理

Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。我对这个词组的翻译是"表现层状态转化"。

如果一个架构符合REST原则,就称它为RESTful架构。

要理解RESTFUL架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思。

1.1.1 资源(Resources)

REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。

所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。

所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。

1.1.2 表现层(Representation)

"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。

比如:文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。

URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。

1.1.3 状态转化(State Transfer)

访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。

互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。

客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。

1.1.4 综述

综合上面的解释,我们总结一下什么是RESTful架构:   (1)每一个URI代表一种资源;   (2)客户端和服务器之间,传递这种资源的某种表现层;   (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。

1.1.5 误区

ESTful架构有一些典型的设计误区。 最常见的一种设计错误,就是URI包含动词。因为"资源"表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中。 举例来说,某个URI是/posts/show/1,其中show是动词,这个URI就设计错了,正确的写法应该是/posts/1,然后用GET方法表示show。 如果某些动作是HTTP动词表示不了的,你就应该把动作做成一种资源。比如网上汇款,从账户1向账户2汇款500元,错误的URI是:      POST /accounts/1/transfer/500/to/2

正确的写法是把动词transfer改成名词transaction,资源不能是动词,但是可以是一种服务:      POST /transaction HTTP/1.1   Host: 127.0.0.1      from=1&to=2&amount=500.00

另一个设计误区,就是在URI中加入版本号:      http://www.example.com/app/1.0/foo   http://www.example.com/app/1.1/foo   http://www.example.com/app/2.0/foo

因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URI。版本号可以在HTTP请求头信息的Accept字段中进行区分(参见Versioning REST Services):      Accept: vnd.example-com.foo+json; version=1.0   Accept: vnd.example-com.foo+json; version=1.1   Accept: vnd.example-com.foo+json; version=2.0 

1.2 django-rest-framework四个主要概念

1.2.1 URLs

URL响应请求的url路由

1.2.2 Request & Responses

REST framework 包括 Request(继承自HttpRequest) 和 Response(TemplateResponse)

1.2.3 Model Serializers

将model 实体转换为Python的dictionaires, 然后 渲染成多个API适用的格式,例如JSON或XML。

1.2.4 Class-based Views

基于class的view可以用于复用代码,提供API到浏览器的显示。

二、 正式开始

2.1 简单流程

创建一个名为 tutorial 的新django项目,然后启动一个名为 quickstart 的新app。

# 创建项目工程目录
~/Desktop/pythonsyscode » mkdir tutorial 

~/Desktop/pythonsyscode » cd tutorial                       

# 创建虚拟开发环境
~/Desktop/pythonsyscode/tutorial » pyenv virtualenv 3.6.3 tutorial3.6.3

# 激活虚拟开发环境
~/Desktop/pythonsyscode/tutorial » pyenv activate tutorial3.6.3 


~/Desktop/pythonsyscode/tutorial » pip install pipenv 

# 安装django 
~/Desktop/pythonsyscode/tutorial » pipenv install django 

# 安装 django-rest-framework
~/Desktop/pythonsyscode/tutorial » pipenv install django-rest-framework

# 创建工程tutorial
~/Desktop/pythonsyscode/tutorial » django-admin startproject tutorial 


~/Desktop/pythonsyscode/tutorial » cd tutorial 

# 创建应用quickstart
~/Desktop/pythonsyscode/tutorial/tutorial » django-admin startapp quickstart    


# 初始化数据库
~/Desktop/pythonsyscode/tutorial/tutorial(master*) » python manage.py migrate   kenwu@kenwus-MBP
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying sessions.0001_initial... OK


  # 创建超级用户
  ~/Desktop/pythonsyscode/tutorial/tutorial(master*) » python manage.py createsuperuser      
Username (leave blank to use 'kenwu'): admin
Email address: code4fs@gmail.com
Password: 
Password (again): 
Superuser created successfully.

2.2 创建数据库模型及数据库同步

# 安装代码高亮插件
(tutorial3.6.3) 
~/Desktop/pythonsyscode/tutorial/tutorial(master*) » pipenv install pygments


# quickstart/models.py
from django.db import models

from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles

# Create your models here.
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',)


# 初始化迁移数据模型,同步数据库
~/Desktop/pythonsyscode/tutorial/tutorial(master*) » python manage.py makemigrations
Migrations for 'quickstart':
  quickstart/migrations/0001_initial.py
    - Create model Snippet
(tutorial3.6.3) ------------------------------------------------------------
~/Desktop/pythonsyscode/tutorial/tutorial(master*) » python manage.py migrate   kenwu@kenwus-MBP
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, quickstart, sessions
Running migrations:
  Applying quickstart.0001_initial... OK 

2.3 创建一个序列化类

quickstart应用目录下创建serializers.py文件。该文件主要目的创建一个序列化类,该类与Django Form类非常相似,并在各种字段中包含类似的验证标志,例如required,max_length,default.

from  rest_framework import serializers
from .models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


class SnippetSerializers(serializers.Serializer):
    """
    为我们的WEBAPI提供一种将代码片段实例序列化和反序列化为诸如Json之类的表示形式
    """
    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):
        """
        根据提供的验证过的数据创建并返回一个新的snippet实例
        :param validated_data:
        :return:
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        根据提供的验证过的数据更新和返回一个已经存在的snippet实例
        :param instance:
        :param validated_data:
        :return:
        """
        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

上面的{'base_template': 'textarea.html'}等同于在django form类中的widget=widgets.Textarea.这对于控制如何显示可浏览器浏览的API特别有用。

接下来我们在pycharm python console中调试下导入几个模块之后创建一些片段实例。

1、首先配置下pycharm 中的python console配置:如下图

2、导入几个模块,然后创建两个代码片段实例:

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='foo = "bar"\n')
snippet.save()

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

3、序列化最后一个实例:

serializer = SnippetSerializer(snippet)
serializer.data

5、将原生数据类型转化为json

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

6、 反序列化,将一个流解析为python原生数据类型dict

from django.utils.six import BytesIO

stream = BytesIO(content)
data = JSONParser().parse(stream)
data
{'id': 2, 'title': '', 'code': 'print "hello world"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}

7、将python原生数据类型dict恢复成正常的对象实例

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>

可以看到,API和django form是多么的相似,当我们开始使用我们的序列化类编写时图的时候,相似性会变得更加明显。

我们也可以序列化查询结果集(querysets)而不是模型实例,只需要为serializer添加一个many=True标志。

serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]

2.4 使用ModelSerializers

我们的SnippetSerializer类中重复了很多包含在Snippet模型类(model)中的信息。像Django 提供了Form 与ModelForm类一样,Rest Framework 包括Serializer与ModelSerializer类。

接下来我们使用ModelSerializer类重构我们的序列化类。

# quickstart/serializer.py

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



# 序列一个非常棒的属性就是可以通过打印序列化器类实例的结构(representation)查看它的所有字段。

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

重要的是要记住,ModelSerializer类并不会做任何特别神奇的事情,它们只是创建序列化器类的快捷方式:

1、一组自动确定的字段。 2、默认简单实现的create()和update()方法。

2.5 使用Serializer来编写常规的Django视图views

目前我们不会使用任何REST框架的其他功能,我们只需将视图作为常规Django视图来编写.

# quickstart/views.py
from django.shortcuts import render

from .models import Snippet
from django.views.decorators.csrf import csrf_exempt
from .serializers import SnippetSerializer
from django.http import HttpResponse
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser


class JSONResponse(HttpResponse):
    """
    继承一个HTTP返回,渲染为JSon返回
    """

    def __init__(self, data, **kwargs):
        content = JSONRenderer().render(data)
        kwargs['content_type'] = 'application/json'
        super(JSONResponse, self).__init__(content, **kwargs)


@csrf_exempt
def snippet_list(request):
    """
    列出所有的code snippet,或者创建一个新的Snippet
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        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)
        return JSONResponse(serializer.errors, status=400)


@csrf_exempt
def snippet_detail(request, pk):
    """
    获取,更新或者删除一个code snippet
    :param request:
    :param pk:
    :return:
    """
    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(data=data)
        if serializer.is_valid():
            serializer.save()
            return JSONResponse(serializer.data)
    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)
# quickstart/urls.py

from django.conf.urls import url
import quickstart.views as views
app_name = 'qk'

urlpatterns = [
    url(r'qk/snippets/$', views.snippet_list),
    url(r'qk/snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]
# tutorial/urls.py
from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include



urlpatterns = [
    path('admin/', admin.site.urls),
    url(r'^', include('quickstart.urls', namespace='qk')),
]

2.6 结果展示


评论(0 ) 点赞(15)


暂未登录,请登录之后发表评论。 QQ