此文档为官方文档的翻译,原文地址:https://www.django-rest-framework.org/tutorial/1-serialization/

01-序列化

介绍

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

创建虚拟环境

  1. mkvritualenv drflearn
  2. pip install django
  3. pip install djangorestframework
  4. pip install pygments # 用于代码高亮

开始

  1. cd ~
  2. django-admin startproject tutorial
  3. cd tutorial
  1. python manage.py startapp snippets
  1. INSTALLED_APPS = [
  2. ...
  3. 'rest_framework',
  4. 'snippets',
  5. ]

step1-创建模型

  1. from django.db import models
  2. from pygments.lexers import get_all_lexers # lexer: 词法分析器
  3. from pygments.styles import get_all_styles
  4. LEXERS = [item for item in get_all_lexers() if item[1]]
  5. LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
  6. STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])
  7. class Snippet(models.Model):
  8. created = models.DateTimeField(auto_now_add=True)
  9. title = models.CharField(max_length=100, blank=True, default='')
  10. code = models.TextField()
  11. linenos = models.BooleanField(default=False)
  12. language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
  13. style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
  14. class Meta:
  15. ordering = ['created']

迁移数据库:

  1. python manage.py makemigrations snippets
  2. python manage.py migrate snippets

step2-创建Serializer类

我们需要开始使用Web API的第一件事是提供一种将代码段实例序列化和反序列化为json等表示形式的方法。

我们可以通过声明与Django的表单非常相似的序列化程序来实现这一点。

在snippets目录中创建一个名为serializers.py的文件并添加以下内容:

  1. from rest_framework import serializers
  2. from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
  3. class SnippetSerializer(serializers.Serializer):
  4. id = serializers.IntegerField(read_only=True)
  5. title = serializers.CharField(required=False, allow_blank=True, max_length=100)
  6. code = serializers.CharField(style={'base_template': 'textarea.html'})
  7. linenos = serializers.BooleanField(required=False)
  8. language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
  9. style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
  10. def create(self, validated_data):
  11. """
  12. 根据已验证的数据,创建并返回一个新的“Snippet”实例。
  13. """
  14. return Snippet.objects.create(**validated_data)
  15. def update(self, instance, validated_data):
  16. """
  17. 根据已验证的数据,更新并返回现有的“Snippet”实例。
  18. """
  19. instance.title = validated_data.get('title', instance.title)
  20. instance.code = validated_data.get('code', instance.code)
  21. instance.linenos = validated_data.get('linenos', instance.linenos)
  22. instance.language = validated_data.get('language', instance.language)
  23. instance.style = validated_data.get('style', instance.style)
  24. instance.save()
  25. return instance

serializer类的第一部分定义了被序列化/反序列化的字段create()update()方法定义在调用 serializer.save()时如何创建或修改完整的实例。

一个serializer类与Django的Form类非常相似,并在各个字段上包含类似的验证标志,例如requiredmax_lengthdefault

字段标志还可以控制序列化程序在某些情况下的显示方式,例如在呈现为HTML时。上面的{'base_template':'textarea.html'}标志相当于在Django的Form类上使用widget=widgets.Textarea。这对于控制可浏览API的显示方式特别有用,我们将在本教程后面看到。

实际上,我们也可以通过使用ModelSerializer类来节省一些时间,我们将在后面看到,但现在我们将保持序列化器定义的直白性。

使用Serializer类

在进一步讨论之前,我们先来熟悉一下使用新的 Serializer 类。让我们进入Django的shell:

  1. python manage.py shell

当进入shell后,首先我们先导入一些类,然后创建一些代码片段:

  1. from snippets.models import Snippet
  2. from snippets.serializers import SnippetSerializer
  3. from rest_framework.renderers import JSONRenderer
  4. from rest_framework.parsers import JSONParser
  5. snippet = Snippet(code='foo = "bar"\n')
  6. snippet.save()
  7. snippet = Snippet(code='print("hello, world")\n')
  8. snippet.save()

我们现在有几个片段实例可以使用。让我们来序列化这些实例中的一个来看看效果:

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

现在,我们已经将模型实例转换为Python内置的数据类型。

我们将数据序列化为JSON格式:

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

反序列化也是类似的。首先,我们将一个流解析为Python原生数据类型:

  1. import io
  2. stream = io.BytesIO(content)
  3. data = JSONParser().parse(stream) # 将数据流解析为Python内置类型

然后,我们将这些原生数据类型恢复到一个完全填充的对象实例中:

  1. serializer = SnippetSerializer(data=data) # 将内置类型赋值给data关键字
  2. serializer.is_valid()
  3. # True
  4. serializer.validated_data
  5. # OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
  6. serializer.save() # 执行save()方法做了两件事:将对象保存到数据库;返回该对象。
  7. # <Snippet: Snippet object (3)>

有没有发现serializer类的API与使用表单非常类似。待会我们编写视图时,你会发现它们越来越像。

我们还可以序列化QuerySet而不是模型实例。只不过我们需要在序列化程序参数中添加一个many=True标志。

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

使用ModelSerializer类

上面我们定义的SnippetSerializer类实在是太繁琐了,受不了。
像ModelForm学习,serializer也有了ModelSerializer类。现在,我们用它重构snippets/serializers.py的代码:

  1. from rest_framework import serializers
  2. from snippets.models import Snippet
  3. # 这才是比较可爱的解决方案
  4. class SnippetSerializer(serializers.ModelSerializer):
  5. class Meta:
  6. model = Snippet
  7. fields = ['id', 'title', 'code', 'linenos', 'language', 'style']

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

  1. from snippets.serializers import SnippetSerializer
  2. serializer = SnippetSerializer()
  3. print(repr(serializer))
  4. # SnippetSerializer():
  5. # id = IntegerField(label='ID', read_only=True)
  6. # title = CharField(allow_blank=True, max_length=100, required=False)
  7. # code = CharField(style={'base_template': 'textarea.html'})
  8. # linenos = BooleanField(required=False)
  9. # language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
  10. # style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...

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

  • 一组自动确定的字段。
  • create()update()方法的简单自动实现。

    step3-使用Serializer类编写常规Django视图函数

    让我们看看如何使用新的序列化器类编写一些API视图。目前,我们不会使用REST框架的任何其他功能,我们只将这些视图编写为常规的Django视图。
  1. from django.http import HttpResponse, JsonResponse
  2. from django.views.decorators.csrf import csrf_exempt
  3. from rest_framework.parsers import JSONParser
  4. from snippets.models import Snippet
  5. from snippets.serializers import SnippetSerializer
  6. # 我们的API的根将是一个支持列出所有现有snippet或创建新snippet的视图。
  7. @csrf_exempt # 如何避开CSRF验证来使用POST方法? 用csrf_exempt装饰一下就OK了~
  8. def snippet_list(request):
  9. """
  10. List all code snippets, or create a new snippet.
  11. """
  12. if request.method == 'GET':
  13. snippets = Snippet.objects.all()
  14. serializer = SnippetSerializer(snippets, many=True)
  15. return JsonResponse(serializer.data, safe=False)
  16. elif request.method == 'POST':
  17. data = JSONParser().parse(request)
  18. serializer = SnippetSerializer(data=data)
  19. if serializer.is_valid():
  20. serializer.save()
  21. return JsonResponse(serializer.data, status=201)
  22. return JsonResponse(serializer.errors, status=400)
  23. # 我们还需要一个对应于单个snippet的视图,该视图可用于检索、更新或删除snippet。
  24. @csrf_exempt
  25. def snippet_detail(request, pk):
  26. """
  27. Retrieve, update or delete a code snippet.
  28. """
  29. try:
  30. snippet = Snippet.objects.get(pk=pk)
  31. except Snippet.DoesNotExist:
  32. return HttpResponse(status=404)
  33. if request.method == 'GET':
  34. serializer = SnippetSerializer(snippet)
  35. return JsonResponse(serializer.data)
  36. elif request.method == 'PUT':
  37. data = JSONParser().parse(request)
  38. serializer = SnippetSerializer(snippet, data=data)
  39. if serializer.is_valid():
  40. serializer.save()
  41. return JsonResponse(serializer.data)
  42. return JsonResponse(serializer.errors, status=400)
  43. elif request.method == 'DELETE':
  44. snippet.delete()
  45. return HttpResponse(status=204)

最后,我们需要将这些视图与URL进行关联。在snippets这个app下创建一个urls.py文件:

  1. from django.urls import path
  2. from snippets import views
  3. urlpatterns = [
  4. # 以下两行代码对url的配置展现了RestfulAPI的精髓。
  5. path('snippets/', views.snippet_list),
  6. path('snippets/<int:pk>/', views.snippet_detail),
  7. ]

值得注意的是,目前有一些边缘检测我们没有妥善处理。如果我们发送格式错误的json,或者如果请求使用的是视图无法处理的方法,那么我们最终将得到500个“服务器错误”响应。不过,现在就这样吧。

我们还需要配置一下根urls.py:

  1. from django.urls import path, include
  2. urlpatterns = [
  3. path('', include('snippets.urls')),
  4. ]

测试成果

退出shell后:

  1. python manage.py runserver
  2. Validating models...
  3. 0 errors found
  4. Django version 4.0,1 using settings 'tutorial.settings'
  5. Starting Development server at http://127.0.0.1:8000/
  6. Quit the server with CONTROL-C.

我们来用Httpie这个工具来测试一下,首先安装这个Python包:

  1. pip install httpie

最终,我们得到了一个snippet列表:

  1. http http://127.0.0.1:8000/snippets/
  2. HTTP/1.1 200 OK
  3. ...
  4. [
  5. {
  6. "code": "foo = \"bar\"\n",
  7. "id": 1,
  8. "language": "python",
  9. "linenos": false,
  10. "style": "friendly",
  11. "title": ""
  12. },
  13. {
  14. "code": "print(\"hello, world\")\n",
  15. "id": 2,
  16. "language": "python",
  17. "linenos": false,
  18. "style": "friendly",
  19. "title": ""
  20. },
  21. {
  22. "code": "print(\"hello, world\")",
  23. "id": 3,
  24. "language": "python",
  25. "linenos": false,
  26. "style": "friendly",
  27. "title": ""
  28. }
  29. ]

我们还可以指定id进行测试:

  1. http http://127.0.0.1:8000/snippets/2/
  2. HTTP/1.1 200 OK
  3. ...
  4. {
  5. "id": 2,
  6. "title": "",
  7. "code": "print(\"hello, world\")\n",
  8. "linenos": false,
  9. "language": "python",
  10. "style": "friendly"
  11. }

大功告成!