Django:DjangoRestFramework drf 开发4

  • Post author:
  • Post category:其他




8. 序列化的使用


序列化和反序列化在项目中的使用



1. 搭建Django项目


1. 创建Django项目


搭建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/)

    测试查询图书1

  • demo3:测试查询图书(指定主键pk查询:127.0.0.1:8000/books/1/)
    查询图书2

  • 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方法中返回的内容,这个比较直观。


如有不足,欢迎指正!



版权声明:本文为weixin_45579026原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。