介绍
本教程将介绍如何创建一个简单的在线代码高亮 Web API。在此过程中,将介绍 Django REST Framework 的各个组件,并让你全面了解它们是如何协同工作的。
本教程内容较为深入,所以在开始之前,你可能需要准备一块饼干和一杯你最喜欢的饮料。如果你只是想快速了解内容,可以查看快速入门文档。
注意 :本教程的代码可在 GitHub 上的 encode/rest-framework-tutorial 仓库中找到。你可以克隆该仓库并查看代码的实际运行情况。
设置新环境
首先,我们来创建一个新的虚拟环境,使用 venv 来确保我们项目的软件包配置与其他项目隔离。
python3 -m venv env
source env/bin/activate
现在我们已处于虚拟环境中,可以安装所需的软件包。
pip install django
pip install djangorestframework
pip install pygments # 我们将使用它进行代码高亮
注意 :如果需要退出虚拟环境,只需输入 deactivate
。更多相关信息请查看 venv 文档。
开始
我们已准备好开始编码。首先,创建一个新的项目供我们使用。
cd ~
django-admin startproject tutorial
cd tutorial
一旦完成,我们就可以创建一个应用程序,用于构建一个简单的 Web API。
python manage.py startapp snippets
需要将我们的新应用 snippets
和 rest_framework
应用添加到 INSTALLED_APPS
中。让我们编辑 tutorial/settings.py
文件:
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 snippets
创建序列化类
我们开始创建 Web API 的第一步是提供一种将代码片段实例序列化和反序列化为如 json
等表示形式的方法。我们可以通过声明序列化类来实现这一点,这与 Django 的表单非常相似。在 snippets
目录中创建一个名为 serializers.py
的文件并添加以下内容。
from rest_framework import serializers
from snippets.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):
"""
给定验证后的数据,创建并返回一个新的 `Snippet` 实例。
"""
return Snippet.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
给定验证后的数据,更新并返回一个现有的 `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()
时如何创建或修改完整的实例。
序列化类与 Django 的 Form
类非常相似,各种字段上包含类似的验证标志,如 required
、max_length
和 default
。
字段标志还可以控制序列化器在某些情况下(例如呈现为 HTML 时)的显示方式。上述 {'base_template': 'textarea.html'}
标志等同于在 Django 的 Form
类上使用 widget=widgets.Textarea
。这对于我们将在教程后面看到的可浏览 API 的显示特别有用。
我们可以通过使用 ModelSerializer
类来节省一些时间,我们将在后面看到这一点,但目前我们将保持序列化定义的明确性。
使用序列化器
在继续之前,我们将熟悉使用我们的新序列化器类。让我们进入 Django shell。
python manage.py 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='foo = "bar"\n')
snippet.save()
snippet = Snippet(code='print("hello, world")\n')
snippet.save()
现在我们有一些代码片段实例可以使用。让我们看看如何序列化其中一个实例。
serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
在这一点上,我们已将模型实例转换为 Python 原生数据类型。要完成序列化过程,我们将数据渲染为 json
。
content = JSONRenderer().render(serializer.data)
content
# b'{"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)
...然后将这些原生数据类型恢复为一个完整的对象实例。
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'}
serializer.save()
#
注意到该 API 与表单的使用有多么相似。当我们开始编写使用序列化器的视图时,这种相似性将更加明显。
我们也可以序列化查询集而不是模型实例。为此,我们只需在序列化器参数中添加一个 many=True
标志。
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [{'id': 1, 'title': '', 'code': 'foo = "bar"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}, {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}, {'id': 3, 'title': '', 'code': 'print("hello, world")', 'linenos': False, 'language': 'python', 'style': 'friendly'}]
使用 ModelSerializers
我们的 SnippetSerializer
类正在复制 Snippet
模型中也包含的大量信息。能够使我们的代码更简洁将是非常好的。
与 Django 提供 Form
类和 ModelForm
类的方式类似,Django REST Framework 包括 Serializer
类和 ModelSerializer
类。
让我们查看使用 ModelSerializer
类重构序列化器的情况。再次打开 snippets/serializers.py
文件,并将 SnippetSerializer
类替换为以下内容。
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ['id', 'title', 'code', 'linenos', 'language', 'style']
序列化器的一个很好的属性是可以通过打印其表示形式来检查序列化器实例中的所有字段。使用 python manage.py shell
打开 Django shell,然后尝试以下操作:
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
类并没有做什么特别神奇的事情,它们只是创建序列化器类的快捷方式:
- 自动确定一组字段。
- 为
create()
和update()
方法提供简单的默认实现。
编写使用我们序列化器的常规 Django 视图
让我们看看如何使用我们的新序列化器类编写一些 API 视图。目前,我们将不使用 Django 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
我们的 API 的根将是一个视图,支持列出所有现有的代码片段或创建一个新的代码片段。
@csrf_exempt
def snippet_list(request):
"""
列出所有代码片段,或创建一个新的代码片段。
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return JsonResponse(serializer.data, safe=False)
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 令牌的客户端 POST 到此视图,因此我们需要将视图标记为 csrf_exempt
。这并不是你通常会想做的事情,Django REST Framework 视图实际上使用了比这更合理的做法,但现在这样对我们来说已经足够了。
我们还需要一个与单个代码片段对应的视图,可以用于检索、更新或删除该代码片段。
@csrf_exempt
def snippet_detail(request, pk):
"""
检索、更新或删除一个代码片段。
"""
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 JsonResponse(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//', views.snippet_detail),
]
我们还需要在根 urlconf 中连接 tutorial/urls.py
文件,以包含我们代码片段应用的 URL。
from django.urls import path, include
urlpatterns = [
path('', include('snippets.urls')),
]
值得一提的是,我们目前还没有正确处理一些边缘情况。如果发送格式错误的 json
,或者使用视图不处理的方法进行请求,那么我们将得到一个 500 “服务器错误” 响应。不过,这已足够我们目前使用。
测试我们的 Web API 初版
现在我们可以启动一个示例服务器来提供我们代码片段服务。
退出 shell...
quit()
...并启动 Django 开发服务器。
python manage.py runserver
验证模型...
未发现错误
Django 版本 5.0,使用设置 'tutorial.settings'
开发服务器启动于 http://127.0.0.1:8000/
使用 CONTROL-C 停止服务器。
在另一个终端窗口中,我们可以测试服务器。
我们可以使用 curl 或 httpie 测试我们的 API。Httpie 是一个用户友好的 Python 编写的 HTTP 客户端。让我们安装它。
可以使用 pip 安装 httpie:
pip install httpie
最后,我们可以通过以下方式获取所有代码片段的列表:
http GET http://127.0.0.1:8000/snippets/ --unsorted
HTTP/1.1 200 OK
...
[
{
"id": 1,
"title": "",
"code": "foo = \"bar\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 3,
"title": "",
"code": "print(\"hello, world\")",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
或者我们可以通过引用代码片段的 id 来获取特定的代码片段:
http GET http://127.0.0.1:8000/snippets/2/ --unsorted
HTTP/1.1 200 OK
...
{
"id": 2,
"title": "",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
同样,通过在网页浏览器中访问这些 URL,你也可以看到相同的 json 内容。
现在我们的情况
到目前为止,我们做得还不错,我们已经拥有一个感觉与 Django 的表单 API 非常相似的序列化 API,以及一些常规的 Django 视图。
我们的 API 视图目前并没有做任何特别的事情,只是提供 json
响应,还有一些错误处理的边缘情况我们仍然希望清理,但它是一个功能正常的 Web API。
我们将在教程的第二部分中看到如何开始改进它。