一、DRF框架之序列化器的使用
1.设置新环境
在我们做任何其他事情之前,我们将使用venv创建一个新的虚拟环境。这将确保我们的软件包配置与我们正在进行的任何其他项目保持良好的隔离。
python3 -m venv env source env/bin/activate
现在,我们已进入虚拟环境,可以安装软件包要求。
pip install django pip install djangorestframework pip install pygments # We'll be using this for the code highlighting
2.创建新项目
让我们创建一个要使用的新项目。
cd ~ django-admin startproject tutorial cd tutorial
完成后,我们可以创建一个应用程序,我们将使用它来创建一个简单的Web API。
python manage.py startapp snippets
我们需要将新应用和应用添加到 .让我们编辑文件:settings.py
INSTALLED_APPS = [ ... 'rest_framework', 'snippets', ]
3.创建模型类
创建模型类
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
4.创建序列化程序类
在 Web API 上,我们需要开始做的第一件事是提供一种将代码段实例序列化和反序列化为表示形式(如 ) 的方法。我们可以通过声明与Django的形式非常相似的序列化程序来做到这一点。在名为的目录中创建一个文件,然后添加以下内容。
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): """ Create and return a new `Snippet` instance, given the validated data. """ return Snippet.objects.create(**validated_data) def update(self, instance, validated_data): """ Update and return an existing `Snippet` instance, given the validated data. """ 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
5.使用序列化程序
在我们进一步使用之前,我们将熟悉如何使用新的序列化程序类。让我们进入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 # OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]) serializer.save() # <Snippet: Snippet object>
请注意,API 与处理表单非常相似。当我们开始编写使用序列化程序的视图时,这种相似性应该变得更加明显。
我们还可以序列化查询集而不是模型实例。为此,我们只需向序列化程序参数添加一个标志即可many=True
serializer = SnippetSerializer(Snippet.objects.all(), many=True) serializer.data # [OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar"\n'), ('linenos', False), ('language', 'python')
6.使用模型序列化程序
序列化程序类
class SnippetSerializer(serializers.ModelSerializer): class Meta: model = Snippet fields = ['id', 'title', 'code', 'linenos', 'language', 'style']
序列化程序具有的一个很好的属性是,您可以通过打印序列化程序实例的表示形式来检查序列化程序实例中的所有字段。使用 打开 Django 外壳程序,然后尝试以下操作:python manage.py 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')...
7.使用我们的序列化程序编写常规 Django 视图
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): """ 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 令牌的客户端发布到此视图,因此需要将视图标记为 。这不是你通常想要做的事情,REST框架视图实际上使用比这更明智的行为,但它现在会满足我们的目的。
我们还需要一个与单个代码段相对应的视图,该视图可用于检索、更新或删除代码段。
@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)
最后,我们需要将这些视图连接起来。创建文件:urls.py
from django.urls import path from snippets import views urlpatterns = [ path('snippets/', views.snippet_list), path('snippets/<int:pk>/', views.snippet_detail), ]
我们还需要在文件中连接根 urlconf,以包含代码段应用的 URL。urls.py
from django.urls import path, include urlpatterns = [ path('', include('snippets.urls')), ]
值得注意的是,我们目前没有正确处理一些边缘情况。如果我们发送格式错误的 ,或者如果使用视图无法处理的方法发出请求,那么我们最终会得到500个“服务器错误”响应。
二、DRF框架之序列化器的嵌套
from django.db import models #定义图书模型类BookInfo class BookInfo(models.Model): btitle = models.CharField(max_length=20, verbose_name='名称',help_text='图书名称') bpub_date = models.DateField(verbose_name='发布日期') bread = models.IntegerField(default=0, verbose_name='阅读量') bcomment = models.IntegerField(default=0, verbose_name='评论量') is_delete = models.BooleanField(default=False, verbose_name='逻辑删除') class Meta: db_table = 'tb_books' # 指明数据库表名 verbose_name = '图书' # 在admin站点中显示的名称 verbose_name_plural = verbose_name # 显示的复数名称 def __str__(self): """定义每个数据对象的显示信息""" return self.btitle #定义英雄模型类HeroInfo class HeroInfo(models.Model): GENDER_CHOICES = ( (0, 'male'), (1, 'female') ) hname = models.CharField(max_length=20, verbose_name='名称') hgender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别') hcomment = models.CharField(max_length=200, null=True, verbose_name='描述信息') hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书',related_name='hero') # 外键 related_name指定父表查询子表的字段 is_delete = models.BooleanField(default=False, verbose_name='逻辑删除') class Meta: db_table = 'tb_heros' verbose_name = '英雄' verbose_name_plural = verbose_name def __str__(self): return self.hname
from rest_framework import serializers # 自定义序列化器 class BookSerializer(serializers.Serializer): """ 图书序列化器 """ # 定义字段 # 默认每个字段都有required=True的选参数,这个参数就是要求前端必须传递字段数据 # allow_null = True配合表单数据使用 id = serializers.IntegerField(read_only=True) # read_only=True 表示该字段只参与序列化返回 btitle = serializers.CharField(max_length=20,min_length=5) bpub_date = serializers.DateField(default="1999-10-10") bread = serializers.IntegerField(min_value=1,max_value=200) bcomment=serializers.IntegerField(write_only=True) # 该字段只参与反序列 is_delete = serializers.BooleanField(required=False) # hname=serializers.CharField() # 关联对象嵌套序列化返回 父表嵌套子表 # PrimaryKeyRelatedField返回关联对象的id值 # heroinfo_set=serializers.PrimaryKeyRelatedField(many=True,read_only=True) # StringRelatedField返回关联对象模型类中的__str__的结果 # heroinfo_set = serializers.StringRelatedField(many=True) # hero = HeroSerializer(many=True) # 自定义验证字段方法 # 单一字段验证 def validate_btitle(self, attrs): if attrs == 'python': raise serializers.ValidationError('书名不能为python') return attrs # 多个字段验证 def validate(self, attrs): if attrs['bread'] > attrs["bcomment"]: raise serializers.ValidationError('阅读量大于评论量') return attrs class HeroSerializer(serializers.Serializer): """ 英雄序列化器 """ # 定义字段 hname = serializers.CharField() hgender = serializers.IntegerField() hcomment = serializers.CharField() is_delete = serializers.BooleanField() # 关联对象嵌套序列化返回 子表嵌套父表 # hbook = serializers.PrimaryKeyRelatedField(read_only=True) # hbook = serializers.StringRelatedField() # hbook = BookSerializer() """ book=BookInfo.objects.get(id=1) book.heroinfo_set.all() """
import json from django.http import JsonResponse from django.shortcuts import render # Create your views here. from django.views import View from book.models import BookInfo from drf_book.serializer import BookSerializer class BooksView(View): """ 获取所有图书和保存图书 """ def get(self, request): """ 获取所有图书 :param request: :return: """ # 1、查询图书表获取所有图书对象 books = BookInfo.objects.all() # 2、提取所有对象的字段内容 # 初始化生成序列化器对象 ser = BookSerializer(books, many=True) # 使用序列化器对象的data方法获取序列化后的结果 data = ser.data # 3、返回所有对象字段内容 return JsonResponse({'book_list': data}) def post(self, request): """ 保存图书 :param request: :return: """ # 1、获取保存的图书数据 data = request.body.decode() data_dict = json.loads(data) # 2、验证图书数据字段 ser=BookSerializer(data=data_dict) # is_valid是序列化器的验证方法 ser.is_valid(raise_exception=True) # raise_exception=True 验证失败直接返回 # ser.errors获取验证结果信息 # 3、保存图书 book = BookInfo.objects.create(btitle=ser.validated_data['btitle'], bpub_date=ser.validated_data['bpub_date']) # 4、返回保存后的图书数据 ser = BookSerializer(book) return JsonResponse(ser.data) class BookView(View): """ 获取单一图书数据 更新图书 删除图书 """ def get(self, request, pk): """ 获取单一图书数据 :param request: :param pk: :return: """ # 1、根据pk值查询图书对象 try: book = BookInfo.objects.get(id=pk) except: return JsonResponse({'error': '错误的id值'}) # 2、返回图书数据 ser = BookSerializer(book) return JsonResponse( ser.data ) def put(self, request, pk): """ 更新图书 :param request: :param pk: :return: """ # 1、获取保存的图书数据 data = request.body.decode() data_dict = json.loads(data) # 2、验证图书数据字段 btitle = data_dict.get('btitle') bpub_date = data_dict.get('bpub_date') if btitle is None or bpub_date is None: return JsonResponse({'error': '缺少必要数据'}) # 3、更新图书 # try: # book = BookInfo.objects.get(id=pk) # except: # return JsonResponse({'error': '错误的id值'}) # # book.btitle=btitle # book.bpub_date=bpub_date # book.save() # {'name':'python'} name=python {'btitle':'书名'} num = BookInfo.objects.filter(id=pk).update(**data_dict) # 4、返回保存后的图书数据 book = BookInfo.objects.get(id=pk) ser = BookSerializer(book) return JsonResponse(ser.data) def delete(self, request, pk): """ 删除图书 :param request: :param pk: :return: """ # 1、查询删除的图书对象 try: book = BookInfo.objects.get(id=pk) except: return JsonResponse({'error': '错误的id值'}) # 2、逻辑 book.is_delete = True book.save() # 物理删除 # book.delete() # 3、返回结果 return JsonResponse({})
总结
下面序列化器源码:
序列化器部分:
from rest_framework import serializers # 自定义序列化器 from book.models import BookInfo class BookSerializer(serializers.Serializer): """ 图书序列化器 """ # 定义字段 # 默认每个字段都有required=True的选参数,这个参数就是要求前端必须传递字段数据 # allow_null = True配合表单数据使用 id = serializers.IntegerField(read_only=True) # read_only=True 表示该字段只参与序列化返回 btitle = serializers.CharField(max_length=20, min_length=5) bpub_date = serializers.DateField(default="1999-10-10") bread = serializers.IntegerField(min_value=1, max_value=200) bcomment = serializers.IntegerField(write_only=True) # 该字段只参与反序列 is_delete = serializers.BooleanField(required=False) # hname=serializers.CharField() # 关联对象嵌套序列化返回 父表嵌套子表 # PrimaryKeyRelatedField返回关联对象的id值 # heroinfo_set=serializers.PrimaryKeyRelatedField(many=True,read_only=True) # StringRelatedField返回关联对象模型类中的__str__的结果 # heroinfo_set = serializers.StringRelatedField(many=True) # hero = HeroSerializer(many=True) # 自定义验证字段方法 # 单一字段验证 def validate_btitle(self, attrs): if attrs == 'python': raise serializers.ValidationError('书名不能为python') return attrs # 多个字段验证 def validate(self, attrs): if attrs['bread'] > attrs["bcomment"]: raise serializers.ValidationError('阅读量大于评论量') return attrs def create(self, validated_data): """ 封装保存数据业务 :param validated_data: 接受验证后的数据 :return: """ book = BookInfo.objects.create(btitle=validated_data['btitle'], bpub_date=validated_data['bpub_date']) # 需要将保存后的对象返回 return book def update(self, instance, validated_data): """ 封装更新数据业务 :param instance: 接受要更新的数据对象 :param validated_data: 接受验证后的数据 :return: """ # BookInfo.objects.filter(id=instance.id).update(**validated_data) instance.btitile = validated_data['btitle'] instance.save() return instance class HeroSerializer(serializers.Serializer): """ 英雄序列化器 """ # 定义字段 hname = serializers.CharField() hgender = serializers.IntegerField() hcomment = serializers.CharField() is_delete = serializers.BooleanField() # 关联对象嵌套序列化返回 子表嵌套父表 # hbook = serializers.PrimaryKeyRelatedField(read_only=True) # hbook = serializers.StringRelatedField() # hbook = BookSerializer() """ book=BookInfo.objects.get(id=1) book.heroinfo_set.all() """ class BookModelSerializer(serializers.ModelSerializer): """ 1、帮助我们自动生成序列化器字段 2、帮助实现create方法和update方法 3、如果在模型类里指定了唯一值参数,会帮助实现唯一值验证方法, """ # 显示指明字段 bcomment = serializers.IntegerField(max_value=100, min_value=5) sms_code=serializers.CharField(max_length=6,min_length=6) class Meta: # 指定根据哪个模型类生成的序列化字段 model = BookInfo # 指定哪些些字段生成序列化器字段 fields = ('id', 'btitle', 'bread', 'bcomment', 'bpub_date','sms_code') # fields = "__all__" # 取反生成字段 # exclude = ('btitle',) # 给指定的字段增加一个read_only=True选项参数 read_only_fields=('bpub_date',) # 添加或修改自动生成选项参数 extra_kwargs = { 'bread': { 'max_value': 100, 'min_value': 5, }, "btitle": { 'min_length': 5 } } # # def validate(self, attrs): # pass
视图部分:
import json from django.http import JsonResponse,HttpRequest,HttpResponse from django.shortcuts import render from rest_framework.request import Request # Create your views here. from django.views import View from book.models import BookInfo from drf_book.serializer import BookSerializer class BooksView(View): """ 获取所有图书和保存图书 """ def get(self, request): """ 获取所有图书 :param request: :return: """ # 1、查询图书表获取所有图书对象 books = BookInfo.objects.all() # 2、提取所有对象的字段内容 # 初始化生成序列化器对象 ser = BookSerializer(books, many=True) # 使用序列化器对象的data方法获取序列化后的结果 data = ser.data # 3、返回所有对象字段内容 return JsonResponse({'book_list': data}) def post(self, request): """ 保存图书 :param request: :return: """ # 1、获取保存的图书数据 data = request.body.decode() data_dict = json.loads(data) # 2、验证图书数据字段 ser=BookSerializer(data=data_dict) # is_valid是序列化器的验证方法 ser.is_valid(raise_exception=True) # raise_exception=True 验证失败直接返回 # ser.errors获取验证结果信息 # 3、保存图书 # book = BookInfo.objects.create(btitle=ser.validated_data['btitle'], bpub_date=ser.validated_data['bpub_date']) # # 4、返回保存后的图书数据 # ser = BookSerializer(book) # 调用序列化器中封装保存方法create ser.save() return JsonResponse(ser.data) class BookView(View): """ 获取单一图书数据 更新图书 删除图书 """ def get(self, request, pk): """ 获取单一图书数据 :param request: :param pk: :return: """ # 1、根据pk值查询图书对象 try: book = BookInfo.objects.get(id=pk) except: return JsonResponse({'error': '错误的id值'}) # 2、返回图书数据 ser = BookSerializer(book) return JsonResponse( ser.data ) def put(self, request, pk): """ 更新图书 :param request: :param pk: :return: """ # 1、获取保存的图书数据 data = request.body.decode() data_dict = json.loads(data) # 2、验证图书数据字段 try: book = BookInfo.objects.get(id=pk) except: return JsonResponse({'error': '错误的id值'}) ser = BookSerializer(book,data=data_dict) # is_valid是序列化器的验证方法 ser.is_valid(raise_exception=True) # raise_exception=True 验证失败直接返回 # 3、更新图书 # num = BookInfo.objects.filter(id=pk).update(**data_dict) # # 4、返回保存后的图书数据 # book = BookInfo.objects.get(id=pk) # ser = BookSerializer(book) # 调用序列化器中封装保存方法update ser.save() return JsonResponse(ser.data) def delete(self, request, pk): """ 删除图书 :param request: :param pk: :return: """ # 1、查询删除的图书对象 try: book = BookInfo.objects.get(id=pk) except: return JsonResponse({'error': '错误的id值'}) # 2、逻辑 book.is_delete = True book.save() # 物理删除 # book.delete() # 3、返回结果 return JsonResponse({})