Django5+DRF序列化

概述

本教程将介绍如何创建一个简单的粘贴板代码高亮 Web API。在此过程中,它将介绍构成 REST 框架的各种组件,让你全面了解所有组件是如何组合在一起的。

本教程相当深入,因此在开始学习之前,你可能需要先吃一块饼干,再喝一杯你最喜欢的啤酒。如果你只想快速了解概况,那就去看看快速入门文档吧。

注意:本文代码基于入门教程的代码继续,如果有不明白的地方,建议先看入门教程的代码。

安装依赖

bash 复制代码
# 之前已经安装的
pip install django
pip install djangorestframework

# 需要新安装的
pip install pygments

创建新的应用

完成后,我们就可以创建一个应用程序,用来创建一个简单的 Web API。

bash 复制代码
python manage.py startapp snippets

注册新的应用:

python 复制代码
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'quickstart',
    'snippets',
    'rest_framework',
]

创建模型

在本教程中,我们将首先创建一个用于存储代码片段的简单片段模型。继续编辑 snippets/models.py 文件。注:良好的编程实践包括注释。虽然您可以在本教程代码的存储库版本中找到注释,但我们在此省略了它们,以专注于代码本身。

python 复制代码
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(verbose_name="创建时间", auto_now_add=True)
    title = models.CharField(verbose_name="标题", max_length=100, blank=True, default='')
    code = models.TextField(verbose_name="代码")
    linenos = models.BooleanField(verbose_name="开启行号", default=False)
    language = models.CharField(verbose_name="编程语言", choices=LANGUAGE_CHOICES, default='python', max_length=100)
    style = models.CharField(verbose_name="代码样式", choices=STYLE_CHOICES, default='friendly', max_length=100)

    class Meta:
        # 根据创建时间升序
        ordering = ['created']

我们还需要为片段模型创建初始迁移,并首次同步数据库。

bash 复制代码
python manage.py makemigrations snippets
python manage.py migrate snippets

创建序列化类

要开始使用 Web API,我们首先需要提供一种将片段实例序列化和反序列化为 json 等表示形式的方法。我们可以通过声明与 Django 的表单非常相似的序列化器来实现这一点。在片段目录中创建一个名为 serializers.py 的文件,并添加以下内容。

python 复制代码
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 表单类非常相似,包括各种字段的类似验证标志,如 required、max_length 和 default。

字段标志还可以控制序列化器在某些情况下的显示方式,例如渲染为 HTML 时。上面的 {'base_template': 'textarea.html'} 标志相当于在 Django 表单类上使用 widget=widgets.Textarea。这对于控制如何显示可浏览 API 尤为有用,我们将在本教程稍后部分看到这一点。

实际上,我们还可以通过使用 ModelSerializer 类来节省时间,稍后我们将看到这一点,但现在我们将保持序列化器定义的明确性。

序列化类的用法

在进一步了解之前,我们先熟悉一下如何使用新的序列化器类。让我们进入 Django shell。

bash 复制代码
python manage.py shell

好了,在我们完成一些导入后,让我们创建几个代码片段来使用。

python 复制代码
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()

现在,我们有了几个片段实例可以使用。让我们看看如何将其中一个实例序列化。

python 复制代码
serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}

至此,我们已将模型实例转化为 Python 本地数据类型。为了最终完成序列化过程,我们将数据渲染为 json。

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

反序列化与此类似。首先,我们将数据流解析为 Python 原生数据类型...

python 复制代码
import io

stream = io.BytesIO(content)
data = JSONParser().parse(stream)

...然后我们将这些本地数据类型还原成一个完全填充的对象实例。

python 复制代码
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 与使用表单是多么相似。当我们开始编写使用序列化器的视图时,这种相似性会变得更加明显。

我们还可以序列化查询集而不是模型实例。为此,我们只需在序列化器参数中添加 many=True 标志即可。

python 复制代码
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')])]

使用模型序列化类

我们的 SnippetSerializer 类正在复制 Snippet 模型中包含的大量信息。如果我们能让代码更简洁一些就更好了。

就像 Django 提供表单类和 ModelForm 类一样,REST 框架也包括序列化器类和模型序列化器类。

让我们看看如何使用 ModelSerializer 类重构序列化器。再次打开文件 snippets/serializers.py,用以下代码替换 SnippetSerializer 类。

python 复制代码
class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ['id', 'title', 'code', 'linenos', 'language', 'style']

序列化器有一个很好的特性,那就是你可以通过打印序列化器实例的表示来检查它的所有字段。使用 python manage.py shell 打开 Django shell,然后尝试以下操作:

python 复制代码
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() 方法的简单默认实现。

完整代码如下:

python 复制代码
from rest_framework import serializers
from snippets.models import Snippet


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

使用我们的序列化器编写常规 Django 视图

让我们看看如何使用新的 Serializer 类编写 API 视图。目前,我们不会使用 REST 框架的任何其他功能,我们只会把视图写成普通的 Django 视图。

编辑 snippets/views.py 文件,添加以下内容。

python 复制代码
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 的根将是一个视图,它支持列出所有现有片段或创建新片段。

python 复制代码
@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, 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。这并不是你通常想要做的事情,REST 框架视图实际上使用了比这更合理的行为,但对于我们现在的目的来说,这已经足够了。

我们还需要一个与单个片段相对应的视图,用于检索、更新或删除片段。

python 复制代码
@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)
        return JsonResponse(serializer.errors, status=400)

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

完整代码如下:

python 复制代码
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


@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_exempt
def snippet_detail(request, pk):
    """
    检索、更新或删除代码片段。
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

    # 根据ID获取代码
    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JsonResponse(serializer.data)

    # 根据ID更新代码
    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)

    # 根据ID删除代码
    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

配置路由

最后,我们需要将这些视图连接起来。创建 snippets/urls.py 文件:

python 复制代码
from django.urls import path
from snippets import views

urlpatterns = [
    path('', views.snippet_list),
    path('<int:pk>/', views.snippet_detail),
]

我们还需要对 tutorial/urls.py 文件中的 urlconf 根目录进行布线,以包含我们的片段应用程序的 URL。

python 复制代码
from django.urls import include, path
from rest_framework import routers

from quickstart import views

# 创建子路由
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)

# 使用自动 URL 路由为我们的应用程序接口布线。
# 此外,我们还提供了可浏览 API 的登录 URL。
urlpatterns = [
    path('', include(router.urls)),
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    path('snippets/', include('snippets.urls')),
]

urlpatterns += router.urls

值得注意的是,有几种边缘情况我们目前还没有处理好。如果我们发送了畸形的 json,或者请求使用了视图无法处理的方法,那么我们最终会收到 500 "服务器错误 "的响应。不过,现在这样也可以。

测试我们的接口

新增代码片段:

bash 复制代码
(venv) PS D:\tmp\drf_demo>  http http://127.0.0.1:8000/snippets/ code=abc
HTTP/1.1 201 Created
Content-Length: 98
Content-Type: application/json
Cross-Origin-Opener-Policy: same-origin
Date: Sun, 07 Jan 2024 04:30:19 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.12.0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "code": "abc",
    "id": 1,
    "language": "python",
    "linenos": false,
    "style": "friendly",
    "title": ""
}

请求所有代码片段:

bash 复制代码
(venv) PS D:\tmp\drf_demo> http http://127.0.0.1:8000/snippets/
HTTP/1.1 200 OK
Content-Length: 100
Content-Type: application/json
Cross-Origin-Opener-Policy: same-origin
Date: Sun, 07 Jan 2024 04:30:35 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.12.0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

[
    {
        "code": "abc",
        "id": 1,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": ""
    }
]


(venv) PS D:\tmp\drf_demo>

总结

到目前为止,我们做得还不错,我们已经有了一个序列化 API,感觉与 Django 的表单 API 和一些常规的 Django 视图非常相似。

目前,我们的 API 视图除了提供 json 响应外,并没有做什么特别的事情,而且我们还想清理一些错误处理的边缘情况,但这已经是一个正常运行的 Web API 了。

我们将在本教程的第二部分看看如何开始改进。

相关推荐
小王子10241 小时前
设计模式Python版 组合模式
python·设计模式·组合模式
Mason Lin3 小时前
2025年1月22日(网络编程 udp)
网络·python·udp
清弦墨客3 小时前
【蓝桥杯】43697.机器人塔
python·蓝桥杯·程序算法
RZer5 小时前
Hypium+python鸿蒙原生自动化安装配置
python·自动化·harmonyos
CM莫问6 小时前
什么是门控循环单元?
人工智能·pytorch·python·rnn·深度学习·算法·gru
查理零世6 小时前
【算法】回溯算法专题① ——子集型回溯 python
python·算法
圆圆滚滚小企鹅。7 小时前
刷题记录 HOT100回溯算法-6:79. 单词搜索
笔记·python·算法·leetcode
纠结哥_Shrek7 小时前
pytorch实现文本摘要
人工智能·pytorch·python
李建军8 小时前
TensorFlow 示例摄氏度到华氏度的转换(二)
人工智能·python·tensorflow
李建军8 小时前
TensorFlow 示例摄氏度到华氏度的转换(一)
人工智能·python·tensorflow