一、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')), ]