分别基于 Django_rest_framework 的 APIView、GenericAPIView 写的增删查改接口

分别基于 Django_rest_framework 的 APIView、GenericAPIView 写的增删查改接口

为演示方便,增删查改接口仅限于单张book表,其他表方法一样,数据库使用的是自带的 sqlite3。

写接口前的准备

创建模型表

# models.py
from django.db import models


class BaseModel(models.Model):
    """
    将图书表、出版社表、作者表都有的三个字段写在一个 BaseModel 类中,直接继承即可
    但是本 BaseModel 类也会生成一张表,如果不想要这张表,可以在内部的 Meta 类中定义 abstract=True
    """
    is_delete = models.BooleanField(default=False)  # 默认是 False,不删除
    # 创建时间
    create_time = models.DateTimeField(auto_now_add=True)
    # auto_now_add=True 只要记录创建,不需要手动插入时间,自动将当前时间插入
    # 另一种写法:
    # import datetime
    # create_time = models.DateTimeField(default=datetime.datetime.now)  # now 不能加括号,不然记录的是每次程序启动的时间

    # 最后更新时间
    last_update_time = models.DateTimeField(auto_now=True)
    # auto_now=True 只要更新,就把当前时间插入

    class Meta:
        # 单个字段,有索引,可唯一
        # 多个字段,有联合索引,可联合唯一
        abstract = True  # 抽象表,不在数据库中建立出表


class Book(BaseModel):
    id = models.AutoField(primary_key=True)  # id 字段可以不写,不写默认也有。但所有的表最好统一,写就都写
    name = models.CharField(max_length=32, help_text="这里填书名")
    price = models.DecimalField(max_digits=5, decimal_places=2)
    """ 外键 """
    publish = models.ForeignKey(to="Publish", on_delete=models.DO_NOTHING, db_constraint=False)
    # to_field 参数默认不用写,作用是关联到 Publish 主键
    # db_constraint=False 表示这个外键只是逻辑上的关联,实质上没有外键联系。但不会影响增删改和 orm 查询
    """
    表断关联:
        1、表之间没有外键关联,但是有外键逻辑关联(有充当外键的字段)
        2、断关联后不会影响数据库查询效率,但是会极大的提高数据库增删改的效率(不影响增删改查操作)
        3、断关联一定要通过逻辑保证表之间数据的安全,不要出现脏数据(一般由程序员在代码层面限制)
        4、断关联后的级联关系:
            作者删掉,作者详情也跟着删掉:on_delete=models.CASCADE
            出版社删掉,图书还在:on_delete=models.DO_NOTHING
            部门删掉,员工还在,员工的部门可以为空:null=True,on_delete=models.SET_NULL
            部门删掉,员工进入默认部门(默认值):default=0, on_delete=models.SET_DEFAULT
    """
    # 多对多的第三张表,只有关联字段时,用自动就够了,有扩展字段,用半自动
    # 因为第三张表一共有三张表互相关联,所以不能写 on_delete
    authors = models.ManyToManyField(to="Author", db_constraint=False)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = "图书表"  # admin 中显示中文表名

    @property
    def publish_name(self):
        return self.publish.name

    def author_list(self):
        author_list = self.authors.all()
        lis = []
        for author in author_list:
            lis.append({"name": author.name, "sex": author.get_sex_display()})
        return lis
        # 也可以用列表推导式一行完成
        # return [{"name": author.name, "sex": author.get_sex_display()} for author in author_list]


class Publish(BaseModel):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=32)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = "出版社表"


class Author(BaseModel):
    name = models.CharField(max_length=32)
    sex = models.IntegerField(choices=((1, "男"), (2, "女")))
    """ 外键 """
    # OneToOneField 本质就是 ForeignKey + unique
    authordetail = models.OneToOneField(to="AuthorDetail", db_constraint=False, on_delete=models.CASCADE)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = "作者表"


class AuthorDetail(BaseModel):
    mobile = models.CharField(max_length=11)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = "作者详情表"

写一个序列化类

# ser.py
from rest_framework import serializers
from api import models


# 写一个类,继承 ListSerializer,重写 update
class BookListSerializer(serializers.ListSerializer):
    # 重写 create 方法,加了一个打印 validated_data 功能
    # def create(self, validated_data):
    #     print(validated_data)
    #     return super().create(validated_data)

    def update(self, instance, validated_data):
        # instance ==> [book对象1, book对象2 ...]
        # validated_data ==> [{name: xx, price: xx}, {name: xx, price: xx} ...]
        # self.child 是 BookModelSerializer 对象,
        # 想保存数据,可以使用 BookModelSerializer 的 update 方法,
        return [
            # self.child.update(对象,字典) for attrs in validated_data
            self.child.update(instance[i], attrs) for i, attrs in enumerate(validated_data)
            # 通过 enumerate 枚举出 validated_data 这个列表的 index(i) 和 字典(attrs)
        ]


class BookModelSerializer(serializers.ModelSerializer):
    # 如何让前端收到的数据显示的是名称而不是id号:
    # 方法一:
    # publish = serializers.CharField(source="publish.name")  # 只序列化可以,反序列化就有问题了
    # 方法二,在 models.py 中对应的表里写一个方法 publish_name 就可以直接放在 fields 中
    """
    @property
    def publish_name(self):
        return self.publish.name
    """

    class Meta:
        list_serializer_class = BookListSerializer
        model = models.Book
        # fields = "__all__"
        # depth = 1  # 展示数据的深度,数字表示展示几层,很少用
        fields = ("name", "price", "authors", "publish", "publish_name", "author_list")
        # 但前端会收到两个 publish,一个是 id 号,一个是名字。解决方法是定义一个为只写,一个为只读
        extra_kwargs = {
            "authors": {"write_only": True},
            "author_list": {"read_only": True},
            "publish": {"write_only": True},
            "publish_name": {"read_only": True},
        }

自定义异常处理和自定义Response

最好在项目文件夹下新建一个 utils 文件夹。
将自定义的异常处理写在 exceptions.py 中,放在 utils 文件夹内。
将自定义的Response写在 response.py 中,放在 utils 文件夹内。
但本次在视图类中没有使用自定义Response,只使用了自定义异常处理。

自定义异常处理

# exceptions.py
from rest_framework.views import exception_handler
from rest_framework import status
from rest_framework.response import Response


def my_exception_handler(exc, context):
    response = exception_handler(exc, context)
    if not response:  # 如果返回的是 None
        # if isinstance(exc, ZeroDivisionError):  # 如果视图类中有特定错误就可以做出相应的返回
        #     return Response(data={"status": 777, "msg": "除以0的错误" + str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        return Response(data={"status": 999, "msg": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
    else:  # 如果返回的是 response 对象
        return Response(data={"status": 888, "msg": response.data.get("detail")}, status=status.HTTP_400_BAD_REQUEST)

自定义Response

# response.py
from rest_framework.response import Response


class APIResponse(Response):
    def __init__(self, code=100, msg="成功", data=None, status=None, headers=None, **kwargs):
        dic = {"code": code, "msg": msg}
        if data:
            dic = {"code": code, "msg": msg, "data": data}
        dic.update(kwargs)
        super().__init__(data=dic, status=status, headers=headers)

全局配置自定义的异常处理

# settings.py
REST_FRAMEWORK = {"EXCEPTION_HANDLER": "utils.exceptions.my_exception_handler"}

配置URL

URL的配置使用了路由分发

# 项目下的 urls.py
from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r"^admin/", admin.site.urls),
    url(r"^api/", include("api.urls"))
]



# 应用下的 urls.py
from django.conf.urls import url
from api import views

urlpatterns = [
    url(r"^books/$", views.BookAPIView.as_view()),
    url(r"^books/(?P<pk>d+)", views.BookAPIView.as_view()),
    url(r"^books2/$", views.Book2APIView.as_view()),
    url(r"^books2/(?P<pk>d+)", views.Book2APIView.as_view()),
]

视图类接口的编写

基于 APIView 的视图类接口

from api import models
from rest_framework.views import APIView
from rest_framework.response import Response
from api.ser import BookModelSerializer


class BookAPIView(APIView):
    def get(self, request, *args, **kwargs):
        """查询单条数据和查询所有数据合在一个方法中"""
        # 查询单条数据
        if kwargs.get("pk", None):
            book = models.Book.objects.filter(pk=kwargs.get("pk")).first()
            book_ser = BookModelSerializer(instance=book)
            return Response(book_ser.data)
        # 查询所有数据
        book_list = models.Book.objects.all().filter(is_delete=False)  # 查询所有没有被删的书
        book_list_ser = BookModelSerializer(book_list, many=True)
        return Response(data=book_list_ser.data)

    def post(self, request, *args, **kwargs):
        """同时具备增单条和增多条的功能"""
        # 如果是字典,就是单条数据新增
        if isinstance(request.data, dict):
            book_ser = BookModelSerializer(data=request.data)
            book_ser.is_valid(raise_exception=True)
            book_ser.save()
            return Response(data=book_ser.data)
        # 如果是列表,就是多条数据新增,列表内部是多个字典(单条数据)
        elif isinstance(request.data, list):
            book_ser = BookModelSerializer(data=request.data, many=True)  # 增多条
            # 有了 many=True 这个参数,book_ser 就是 ListSerializer 的对象了
            # print(type(book_ser))  # 输出:<class "rest_framework.serializers.ListSerializer">
            book_ser.is_valid(raise_exception=True)
            book_ser.save()  # ListSerializer 的 create 方法
            """
            create 方法内部就是一个for循环,self.child 就是 BookModelSerializer 对象
            也就是说,增多条的 ListSerializer 内部其实就是for循环了一个个增单条的 BookModelSerializer

            def create(self, validated_data):
                return [
                    self.child.create(attrs) for attrs in validated_data
                ]
            """
            return Response(data=book_ser.data)

    def put(self, request, *args, **kwargs):
        """同时具备单条修改和多条修改功能"""
        # 单条数据修改
        if kwargs.get("pk", None):
            book = models.Book.objects.filter(pk=kwargs.get("pk")).first()
            book_ser = BookModelSerializer(instance=book, data=request.data, partial=True)
            # partial=True:允许局部修改,如只修改书名和价格
            # 如果不写这个字段想达到同样的效果,要在 ser.py 的 extra_kwargs 中给每个字段都加上 require=False
            book_ser.is_valid(raise_exception=True)
            book_ser.save()
            return Response(data=book_ser.data)
        # 多条数据修改
        else:
            """
            前端传来的数据格式必须是:[{id: 1, name: xx, price: xx}, {id: 2, name: xx, price: xx}]
            要先将数据处理成:对象列表[book对象1, book对象2] 和 要修改的数据列表[{name: xx, price: xx}, {name: xx, price: xx}]
            """
            book_list = []
            modify_data = []
            for item in request.data:
                # 前端传来的 request.data 可能不是列表套字典的格式,直接处理可能会报错,所以最好有全局的异常处理进行捕获
                pk = item.pop("id")
                book = models.Book.objects.get(pk=pk)
                book_list.append(book)
                modify_data.append(item)
            """
            处理好后再修改数据,有两种方案:
            第一种方案:
                直接在视图里写,简单
                for循环 book_ser = BookModelSerializer(instance=对象列表, data=修改的数据列表) 一个一个修改。
            """
            # for i, is_data in enumerate(modify_data):
            #     book_ser = BookModelSerializer(instance=book_list[i], data=is_data)
            #     book_ser.is_valid(raise_exception=True)
            #     book_ser.save()
            # return Response("成功")
            """ 
            第二种方案:
                在序列化类中写,不常用
                book_ser = BookModelSerializer(instance=对象列表, data=修改的数据列表, many=True)
                但要重写 ListSerializer 的 update 方法,
                因为此时 book_ser 是 ListSerializer 的对象,而源码中没写 update 方法,
                需要自己写一个类继承 ListSerializer,里面重写 update 方法。
                问题在于如何让 BookModelSerializer 和自己写的类建立联系?
                查看源码可知,ListSerializer 的父类 BaseSerializer 的 __new__() 中调用了 many_init()
                many_init() 内部的如下代码通过反射,在meta中找不到 list_serializer_class 时才会走 ListSerializer
                list_serializer_class = getattr(meta, "list_serializer_class", ListSerializer)
                因此可以在 ser.py 中自己写的 BookModelSerializer 的 Meta 类中定一个变量叫 list_serializer_class,
                把自己写的 BookListSerializer 这个类赋给这个变量,在 BookListSerializer 内重写 update 方法
                这样只要修改数据时 BookModelSerializer 传了 many=True,就走的是 BookListSerializer 的 update 方法
                同样,如果重写了 create 方法,新增数据时只要传了 many=True,就走的是 BookListSerializer 的 create 方法
            """
            book_ser = BookModelSerializer(instance=book_list, data=modify_data, many=True)
            book_ser.is_valid(raise_exception=True)
            book_ser.save()
            return Response(book_ser.data)

    def delete(self, request, *args, **kwargs):
        """单个删除和批量删除"""
        pk = kwargs.get("pk")  # 如果是单条删除,pk=要删除数据的id值,多条删除,pk=None
        pks = []
        if pk:  # 如果有值,说明是单条删除
            pks.append(pk)  # 即使是单条数据也放在一个列表中,和多条删除用同一个方法处理
        else:  # 如果没有值,是多条删除
            # 如果是多条删除,前端发来的数据格式必须是:{"pks": [要删除的多条数据的id值]},这是下面一行代码规定的
            # 获取到要删除的多条id值组成的列表
            pks = request.data.get("pks")
        # 最后,不论是单条还是多条删除,pks都是一个列表,里面是要删除数据的id值
        ret = models.Book.objects.filter(pk__in=pks, is_delete=False).update(is_delete=True)
        """
        pk__in:后面必须等于一个列表,是依次拿出列表里的每一项
        is_delete=False:确保之前没有被删除过的数据被过滤出来
        .update(is_delete=True):用 update 方法加上 is_delete=True 来标识要被删除的数据
        ret:返回值是受影响的行数(如果是0,表示要删的数据之前都已被标识为已删除了)
        """
        if ret:
            return Response(data={"msg": "删除成功!删除了%s条数据" % ret})
        return Response(data={"msg": "没有要删除的数据..."})

基于 GenericAPIView 的视图类接口

from api import models
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from api.ser import BookModelSerializer


class Book2APIView(GenericAPIView):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer

    def get(self, request, *args, **kwargs):
        """单查和群查"""
        if kwargs.get("pk"):
            book = self.get_object()
            book_ser = self.get_serializer(book)
            return Response(book_ser.data)
            # 查询所有数据
        book_list = self.get_queryset().filter(is_delete=False)  # 查询所有没有被删的书
        book_list_ser = self.get_serializer(book_list, many=True)
        return Response(data=book_list_ser.data)

    def post(self, request, *args, **kwargs):
        """单增和群增"""
        if isinstance(request.data, dict):
            book_ser = self.get_serializer(data=request.data)
            book_ser.is_valid(raise_exception=True)
            book_ser.save()
            return Response(data=book_ser.data)
        elif isinstance(request.data, list):
            book_ser = self.get_serializer(data=request.data, many=True)
            book_ser.is_valid(raise_exception=True)
            book_ser.save()
            return Response(data=book_ser.data)

    def put(self, request, *args, **kwargs):
        """单修和群修"""
        if kwargs.get("pk"):
            book = self.get_object()
            book_ser = self.get_serializer(instance=book, data=request.data, partial=True)
            book_ser.is_valid(raise_exception=True)
            book_ser.save()
            return Response(data=book_ser.data)
        else:
            book_list = []
            modify_data = []
            for item in request.data:
                pk = item.pop("pk")
                book = self.get_queryset().get(pk=pk)
                # book = self.get_queryset().filter(pk=pk).first()  # 这样写也可以
                book_list.append(book)
                modify_data.append(item)
            book_ser = self.get_serializer(instance=book_list, data=modify_data, many=True, partial=True)
            book_ser.is_valid(raise_exception=True)
            book_ser.save()
            return Response(book_ser.data)

    def delete(self, request, *args, **kwargs):
        """单删和多删"""
        pk = kwargs.get("pk")
        pks = []
        if pk:
            pks.append(pk)
        else:
            pks = request.data.get("pks")
        ret = self.get_queryset().filter(pk__in=pks, is_delete=False).update(is_delete=True)
        if ret:
            return Response(data={"msg": "删除成功!删除了%s条数据" % ret})
        return Response(data={"msg": "没有可删除的数据..."})