本文通过创建一个简单的,能高亮显示文本代码的工具的web api,介绍组成REST framework的哥哥组成部分。并对整个框架有一个全面的理解,了解它们是如何组成一体的。
设置新环境
我们首先使用venv创建一个新的虚拟环境,这可以保证我们的当前工作环境下的包配置和其它的项目隔离开来。
python3 -m venv envsource env/bin/activate
进入到虚拟环境中,我们需要安装一些依赖包。
pip install django pip install djangorestframework pip install pygments # We'll be using this for the code highlighting
新建项目
现在可以开始我们的编码,我们创建一个新的项目:
cd ~ django-admin startproject tutorial cd tutorial
在项目下,新建一个APP:
django-admin startapp snippets
我们还需要添加我们的app和rest_framework app到tutorial/setting.py文件的INSTALL_APPS中:
INSTALLED_APPS = ( ... 'rest_framework', 'snippets', )
完成这些配置之后,我们可以正式开始了。
创建模型类
我们可以新建一个简单的Snippet模型类,用于存储代码片段。我们直接在snippets/models.py文件中进行编码。
另外,在写代码的时候,我们最好又良好的写注释的习惯,但是在后面,我先集中经历在代码上,就暂时先不写注释了。
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, 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',)
创建好模型之后,我们同步一下数据库:
python manage.py makemigrations snippets python manage.py migrate
创建序列化类
我们创建Web API的第一件事情就是要为我们的snippet实例提供序列化和反序列化的方法,将实例序列化为json等数据表现形式,或者从json等数据表现形式反序列化为实例。
我可以通过创建serializers来实现。serializer与Django的form表单很相似。
我们可以在snippets文件夹下新近啊一个serializer.py的文件,并添加如下代码:
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): """ :param validated_data: 用于创建Snippet实例的参数 :return: 类Snippet的实例 """ return Snippet.objects.create(**validated_data) def update(self, instance, validated_data): """ :param instance: Snippet实例 :param validated_data: 用于更新Snippet实例的参数 :return: 更新后的Snippet实例 """ 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
这个类的第一部分,我们定义了想要序列化/反序列化的域。create和update方法定义了实例在调用serializer.save()方法时候,实例是如何被创建或修改的。
Serializer类和Django的From类很相似,包括对一些用于判断域的参数,比如required, max_length以及default。
这些域的参数也可以控制我们的serializer序列化器如何在特定的环境下显示,比如当提供给HTML去渲染时。
参数{'base_template': 'textarea.html'}与Django的Form类中的widget=widgets.Textarea。这在我们的可视化的API中很有用,可以来控制我们的API如何显示。这个我们后面会看一下。
我们也可以直接i使用ModelSerializer类来简化代码。
使用序列化器Serializers
在进一步进行我们的项目之前,我们先来使用熟悉一下Serializer类。
我们进入到Django shell:
python manage.py shell
我们先import一些类,并创建几个实例:
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()
有了这些snippet实例之后,我们现在可以对它们进行序列化。
serializer = SnippetSerializer(snippet) serializer.data # {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
反序列化的过程类似。
首先,我们将数据流解析为Python原生数据类型。
import io stream = io.BytesIO(content) data = JSONParser().parse(stream)
然后,我们将这些数据存储到我们的snippet实例中。
serializer = SnippetSerializer(data=data) serializer.is_valid() # Trueserializer.validated_data# OrderedDict([('title', ''), ('code', 'print("hello, world!")'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]) serializer.save() # <Snippet: Snippet object (3)>
通过以上的表现,我们应该可以发现和forms很相似。后面我们在编写视图函数views的时,两者的相似性会更加明显。
除了序列化模型实例之外,我们还可以序列化querysets。序列化querysets的时,我们需要添加一个参数many=true。
serializer = SnippetSerializer(Snippet.objects.all(), many=True) serializer.data # [OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', ''), ('code', 'print("hello, world")'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
使用模型序列化器ModelSerializers
在上面的SnippetSerializer类中,我们从Snippet模型中复制了大量的代码过来。其实,我们可以使这些代码更简明一些。
跟Django中提供了Form和ModelForm两个类相似,REST framework也提供了Serializer类和ModelSerializer类。
我们现在使用ModelSerializer类来重写一下我们的SnippetSerializer类。
打开snippets/serializer.py文件,将SnippetSerializer类修改为:
class SnippetSerializer(serializers.ModelSerializer): class Meta: model = Snippet fields = ('id', 'title', 'code', 'linenos', 'language', 'style')
实例serializer有一个很不错的特点功能,我们可以通过内置的repr函数来打印出来实例中的所有域。
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类并没有很特殊的地方,它只不过是用于创建serializer类的一个捷径而已:
通过集合自动确定域;
帮我们创建默认的create()和update()方法。
使用Serializer写常规的Django视图
现在我们看一下,如果通过我们的Serializer类来写一些API views。
现在,我们先不使用REST framework的一些功能,就先简单写一些常规的Django视图。
编辑snippets/views.py文件:
from django.http import HttpResponse, 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
首先一个视图,我们需要能列出所有已创建的snippet实例,并能创建新的snippet实例。
@csrf_exempt def snippet_list(request): """ 列出所有的snippet,或者创建一个新的snippet :param request: :return: JsonResponse """ if request.method == 'GET': snippets = Snippet.objects.all() serializer = SnippetSerializer(snippets, many=True) return JsonResponse(serializer.data, safe=False) if 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)
我们现在需要从客户端通过POST提交数据到我们的视图函数,我们需要先给我们的视图函数添加一个csrf_exempt的装饰器。
我们还需要一个视图函数来获取一个snippet实例,对实例进行显示,更新或这删除。
@csrf_exempt def snippet_detail(request, pk): """ Retrieve, update or delete a code snippet. :param request: :return: HttpResponse or JsonResponse """ 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) return HttpResponse(serializer.errors, status=400) elif request.method == 'DELETE': snippet.delete() return HttpResponse(status=204)
然后,我们创建snippets/urls.py文件中,写入:
from django.urls import path from snippets import views urlpatterns = [ path('snippets/', views.snippet_list), path('snippets/<int:pk>/', views.snippet_detail), ]
在tutorial/urls.py文件中include上面的文件:
from django.urls import path, include urlpatterns = [ path('', include('snippets.urls')), ]
Web API的测试
我们现在开启我们的web服务。
从shell中退出
quit()
然后,开启Django服务器:
python manage.py runserver Validating models... 0 errors found Django version 1.11, using settings 'tutorial.settings'Development server is running at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
另外打开一个命令行窗口中,我们可以来测试这个服务器。
我们可以使用curl或者httpie来测试。Httpie是一个基于Python的比较好用的一个http客户端。
我们使用pip安装httpie:
pip install httpie
然后,我们可以通过输入http http://127.0.0.1:8000/snippets/ 命令获取一下所有的snippet:
http http://127.0.0.1:8000/snippets/ HTTP/1.1 200 OK Content-Length: 356 Content-Type: application/json Date: Mon, 15 Jul 2019 08:43:44 GMT Server: WSGIServer/0.2 CPython/3.6.0 X-Frame-Options: SAMEORIGIN [ { "code": "foo = \"bar\"\n", "id": 1, "language": "python", "linenos": false, "style": "friendly", "title": "" }, { "code": "print(\"hello, world!\")\n", "id": 2, "language": "python", "linenos": false, "style": "friendly", "title": "" }, { "code": "print(\"hello, world!\")", "id": 3, "language": "python", "linenos": false, "style": "friendly", "title": "" } ]
或者通过id去获取一个特定的snippet:
http http://127.0.0.1:8000/snippets/2/ HTTP/1.1 200 OK Content-Length: 121 Content-Type: application/json Date: Mon, 15 Jul 2019 08:44:07 GMT Server: WSGIServer/0.2 CPython/3.6.0 X-Frame-Options: SAMEORIGIN { "code": "print(\"hello, world!\")\n", "id": 2, "language": "python", "linenos": false, "style": "friendly", "title": "" }
我们也可以通过在浏览器中输入这些url地址来查看到这些json数据。
通过以上,我们创建按了序列化API,这一点和Django的Form很类似。然后我还创建了一些常规的Django视图。
在当前,我们的API视图除了提供json数据的展示之外,还没有什么特别的地方。
后面,我们再来继续完善它。
本文中的源码可以在Github上查看。点我查看本文源码