8. 序列化的使用
序列化和反序列化在项目中的使用
1. 搭建Django项目
1. 创建Django项目
2. 创建子应用
(venv-djdrf) jason93@92 djangodrf % python manage.py startapp booktest
2. 安装和配置
1. 安装第三方包
(venv-djdrf) jason93@92 djangodrf % pip install django
(venv-djdrf) jason93@92 djangodrf % pip install djangorestframework
(venv-djdrf) jason93@92 djangodrf % pip install pymysql
2. 配置
- settings.py
# 配置子应用
INSTALLED_APPS = [
......
'rest_framework',
'booktest.apps.BooktestConfig',
]
# 注释csrf中间件
MIDDLEWARE = [
......
# 'django.middleware.csrf.CsrfViewMiddleware',
......
]
# 配置mysql数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django_drf',
'USER': 'root',
'PASSWORD': '12345678',
'HOST': 'localhost',
'PORT': 3306
}
}
- 项目__init__.py
import pymysql
pymysql.install_as_MySQLdb()
- 项目urls.py
from django.urls import path, include
urlpatterns = [
......
path('', include('booktest.urls')), # Django2版本后路由是path匹配
]
-
终端启动mysql,创建名为
django_drf
的数据库:
jason93@92 ~ % mysql -u root -p12345678 # 进入mysql数据库
......
mysql> create database django_drf charset=utf8; # 创建数据库,指明编码格式为utf-8
Query OK, 1 row affected, 1 warning (0.01 sec) # 创建成功
-
创建所需文件
- booktest子应用中分别创建 urls.py、serializers.py
3. 项目开发
1. 编写模型类
- models.py
from django.db import models
class Book(models.Model):
""" 图书模型类 """
book_name = models.CharField(max_length=64, unique=True, verbose_name='书名')
author = models.CharField(max_length=32, verbose_name='作者')
type = models.CharField(max_length=32, verbose_name='分类')
read_count = models.IntegerField(default=0, verbose_name='阅读量')
comment_count = models.IntegerField(default=0, verbose_name='评论量')
published_date = models.DateField(verbose_name='出版日期')
date_joined = models.DateField(auto_now_add=True, verbose_name='新增日期')
is_deleted = models.IntegerField(default=0, verbose_name='是否删除') # 此处做的是逻辑删除,该字段也可以不设,直接物理删除
class Meta:
db_table = 'books'
verbose_name = '图书表'
verbose_name_plural = verbose_name
ordering = ['-date_joined']
def __str__(self):
return self.book_name
class Hero(models.Model):
""" 英雄模型类 """
name = models.CharField(max_length=32, verbose_name='英雄名')
gender = models.CharField(max_length=6, choices=(('male', '男'), ('female', '女')), default='male', verbose_name='性别')
age = models.IntegerField(verbose_name='年龄')
skill = models.CharField(max_length=1024, verbose_name='技能')
related_book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='hero', verbose_name='关联图书') # 外键
date_joined = models.DateField(auto_now_add=True, verbose_name='新增日期')
is_deleted = models.IntegerField(default=0, verbose_name='是否删除')
class Meta:
db_table = 'heros'
verbose_name = '英雄表'
verbose_name_plural = verbose_name
ordering = ['-date_joined']
def __str__(self):
return self.name
2. 迁移模型类
(venv-djdrf) jason93@92 djangodrf % python manage.py makemigrations
Migrations for 'booktest':
booktest/migrations/0001_initial.py
- Create model Book
- Create model Hero
(venv-djdrf) jason93@92 djangodrf % python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, booktest, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying booktest.0001_initial... OK
Applying sessions.0001_initial... OK
3. 设计序列化器
根据模型类设计序列化器
- serializers.py
from rest_framework import serializers
from .models import Book, Hero
class BookSerializer(serializers.Serializer):
""" 图书序列化器 """
id = serializers.IntegerField(read_only=True, label='图书编号')
book_name = serializers.CharField(min_length=2, max_length=64, allow_blank=False, help_text='请输入图书名',
error_messages={"min_length": "图书名不能少于2个字", "max_length": "图书名不能多于64个字"})
author = serializers.CharField(max_length=32)
type = serializers.CharField(max_length=32)
read_count = serializers.IntegerField(default=0, required=False)
comment_count = serializers.IntegerField(default=0, required=False)
published_date = serializers.DateField()
date_joined = serializers.DateField(read_only=True)
is_deleted = serializers.IntegerField(default=0)
hero = serializers.StringRelatedField(read_only=True, many=True) # 英雄模型类中定义外键时指定related_name,book序列化器可引用
def validate_book_name(self, attr):
""" 验证图书名是否唯一 """
# 查询数据库是否由该图书
if Book.objects.filter(book_name=attr).first():
# 存在则抛出异常信息
raise serializers.ValidationError("该图书已存在")
# 不存在则把attr返回
return attr
def validate(self, attrs):
""" 验证阅读量是否比评论量多 """
# 分别从attrs(字典)中获取阅读量和评论量
read_count = attrs.get('read_count')
comment_count = attrs.get('comment_count')
# 由于阅读量和评论量都不是必需参数,所以判断用户是否把阅读量和评论量都上传
if all([read_count, comment_count]):
# 如果阅读量小于评论量则抛出异常信息,否则返回attrs
if read_count < comment_count:
raise serializers.ValidationError("阅读量不能低于评论量")
return attrs
def create(self, validated_data):
""" 创建 """
# 使用orm创建图书并返回
return Book.objects.create(**validated_data)
def update(self, instance, validated_data):
""" 修改 """
# instance 是被修改都图书对象,validated_data是接收到的需要修改的数据字典
instance.book_name = validated_data.get('book_name')
instance.author = validated_data.get('author')
instance.type = validated_data.get('type')
instance.read_count = validated_data.get('read_count')
instance.comment_count = validated_data.get('comment_count')
instance.published_date = validated_data.get('published_date')
instance.is_deleted = validated_data.get('is_deleted')
# 修改完成后保存
instance.save()
# 把保存后的图书对象返回
return instance
class HeroSerializer(serializers.Serializer):
""" 英雄序列化器 """
id = serializers.IntegerField(read_only=True, label='英雄编号')
name = serializers.CharField(max_length=32)
gender = serializers.CharField(max_length=6, default='male')
age = serializers.IntegerField()
skill = serializers.CharField(max_length=1024)
related_book_id = serializers.IntegerField(required=True) # 外键
date_joined = serializers.DateField(read_only=True)
is_deleted = serializers.IntegerField(default=0)
def create(self, validated_data):
""" 创建 """
return Hero.objects.create(**validated_data)
def update(self, instance, validated_data):
""" 修改 """
instance.name = validated_data.get('name')
instance.gender = validated_data.get('gender')
instance.age = validated_data.get('age')
instance.skill = validated_data.get('skill')
instance.related_book_id = validated_data.get('related_book_id')
instance.is_deleted = validated_data.get('is_deleted')
instance.save()
return instance
4. 编写视图函数
-
views.py
- 先看一下视图函数的大致结构:
from django.views import View
from django.http import JsonResponse
from .models import Book, Hero
from .serializers import BookSerializer, HeroSerializer
import json
class BooksView(View):
""" 图书类视图 """
def get(self, request):
""" 获取所有图书信息 """
# 接收
# 验证
# 处理
# 响应
pass
def post(self, request):
""" 新增图书 """
# 接收
# 验证
# 处理
# 响应
pass
class BookView(View):
""" 图书类视图(带主键) """
def get(self, request, pk):
""" 根据主键pk获取一个图书的信息 """
# 接收
# 验证
# 处理
# 响应
pass
def put(self, request, pk):
""" 根据主键pk修改一个图书的信息 """
# 接收
# 验证
# 处理
# 响应
pass
def delete(self, request, pk):
""" 根据主键pk删除一个图书 """
# 接收
# 验证
# 处理
# 响应
pass
class HerosView(View):
""" 英雄类视图 """
def get(self, request):
""" 获取所有英雄信息 """
# 接收
# 验证
# 处理
# 响应
pass
def post(self, request):
""" 新增英雄 """
# 接收
# 验证
# 处理
# 响应
pass
class HeroView(View):
""" 英雄类视图(带主键) """
def get(self, request, pk):
""" 根据主键获取英雄信息 """
# 接收
# 验证
# 处理
# 响应
pass
def put(self, request, pk):
""" 根据主键修改英雄信息 """
# 接收
# 验证
# 处理
# 响应
pass
def delete(self, request, pk):
""" 根据主键删除英雄信息 """
# 接收
# 验证
# 处理
# 响应
pass
具体的实现如下:
from django.views import View
from django.http import JsonResponse
from .models import Book, Hero
from .serializers import BookSerializer, HeroSerializer
import json
class BooksView(View):
""" 图书类视图 """
def get(self, request):
""" 获取所有图书信息 """
# 接收
# 验证
# 处理
book = BookSerializer(Book.objects.all(), many=True).data
# 响应
return JsonResponse(book, safe=False)
def post(self, request):
""" 新增图书 """
# 接收
params = json.loads(request.body.decode())
# 验证:验证通过则保存并返回序列化后的数据;验证失败返回错误信息
serializer = BookSerializer(data=params)
if serializer.is_valid():
# 处理:保存并创建book对象
book = serializer.save()
# 响应
return JsonResponse(BookSerializer(book).data, status=201)
else:
return JsonResponse(serializer.errors)
class BookView(View):
""" 图书类视图(带主键) """
def get(self, request, pk):
""" 根据主键pk获取一个图书的信息 """
# 接收、验证
try:
book = Book.objects.get(pk=pk)
except Exception as e:
return JsonResponse({"error": str(e)})
# 处理、响应
return JsonResponse(BookSerializer(book).data)
def put(self, request, pk):
""" 根据主键pk修改一个图书的信息 """
# 接收
params = json.loads(request.body.decode())
try:
book_obj = Book.objects.get(pk=pk)
except Exception as e:
return JsonResponse({"error": str(e)})
else:
# 验证
serializer = BookSerializer(book_obj, data=params)
if serializer.is_valid():
# 处理
book = serializer.save()
# 响应
return JsonResponse(BookSerializer(book).data, status=201)
else:
return JsonResponse(serializer.errors)
def delete(self, request, pk):
""" 根据主键pk删除一个图书 """
# 接收、验证、处理
try:
Book.objects.get(pk=pk).delete()
# 响应
return JsonResponse({}, status=204)
except Exception as e:
return JsonResponse({"error": str(e)})
class HerosView(View):
""" 英雄类视图 """
def get(self, request):
""" 获取所有英雄信息 """
# 接收
# 验证
# 处理
hero = HeroSerializer(Hero.objects.all(), many=True).data
# 响应
return JsonResponse(hero, safe=False)
def post(self, request):
""" 新增英雄 """
# 接收
params = json.loads(request.body.decode())
# 验证
serializer = HeroSerializer(data=params)
if serializer.is_valid():
# 处理
hero = serializer.save()
# 响应
return JsonResponse(HeroSerializer(hero).data, status=201)
else:
return JsonResponse(serializer.errors)
class HeroView(View):
""" 英雄类视图(带主键) """
def get(self, request, pk):
""" 根据主键获取英雄信息 """
# 接收、验证
try:
hero = Hero.objects.get(pk=pk)
except Exception as e:
return JsonResponse({"error": str(e)})
# 处理、响应
return JsonResponse(HeroSerializer(hero).data)
def put(self, request, pk):
""" 根据主键修改英雄信息 """
# 接收
params = json.loads(request.body.decode())
try:
hero_obj = Hero.objects.get(pk=pk)
except Exception as e:
return JsonResponse({"error": str(e)})
else:
# 验证
serializer = HeroSerializer(hero_obj, data=params)
if serializer.is_valid():
# 处理
hero = serializer.save()
# 响应
return JsonResponse(HeroSerializer(hero).data, status=201)
else:
return JsonResponse(serializer.errors)
def delete(self, request, pk):
""" 根据主键删除英雄信息 """
# 接收、验证、处理
try:
Hero.objects.get(pk=pk).delete()
# 响应
return JsonResponse({}, status=204)
except Exception as e:
return JsonResponse({"error": str(e)})
5. 配置子路由
- urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^books/$', views.BooksView.as_view()),
url(r'^books/(?P<pk>\d+)/$', views.BookView.as_view()),
url(r'^heros/$', views.HerosView.as_view()),
url(r'^heros/(?P<pk>\d+)/$', views.HeroView.as_view()),
]
6. 测试项目
这里使用一款现阶段依旧很流行的测试软件:postman,可以发送GET、POST、PUT、DELETE等多种请求方式。
-
demo1:测试新增图书
-
demo2:测试查询图书(查询所有:127.0.0.1:8000/books/)
-
demo3:测试查询图书(指定主键pk查询:127.0.0.1:8000/books/1/)
-
demo4:再新增一个查所有:
{ "book_name": "神雕侠侣", "author": "金庸", "type": "古装武侠剧", "read_count": 5, "published_date": "1986-05-02", "is_deleted": 0 }
-
demo5:根据主键pk值修改
可以看到状态是201,这时再查询pk=2的图书信息:
修改成功 -
demo6:删除图书对象
可以看到返回状态码是204,返回信息是个空字典,这也是我们在代码中设计的。
这时再查看所有的图书:
发现id=2的图书没有了,说明删除成功。
以下测试一些错误尝试:
-
demo1:创建已经存在的图书:
-
demo2:查询刚才被删掉id=2的图书:
我们把捕捉到的错误信息转换成字符串作为error的值。翻译为:
图书匹配查询不存在
。 -
demo3:修改一个不存在的图书
- 这里要说明一下,put方法中我们把 Book.objects.get(pk=pk) 作为异常处理的对象放到try内所以不存在的时候会报错,这儿不能使用filter()方法,因为即使查询不到也不会报错而是返回空。
提示查询结果不存在,即id=2的图书不存在。 -
demo4:删除不存在的图书对象
也是报查询结果不存在。
英雄类的测试
暂时就不展示了,有兴趣的Partner可以自己测试一下,而且还可以指定关联图书的ID获取关系属性值。视图函数中我设置返回的是str方法中返回的内容,这个比较直观。
如有不足,欢迎指正!