drf基于APIView实现一对多(ForeignKey)数据新增、更新
之前一直有在看源码,但是其实没有实战过,所以这个地方我就一直没有更新。刚好最近有个项目在做管理后台的时候用到了。我就抽空整理处理做个备忘。这个在日常使用中还是非常频繁的。
我们就以 书籍、作者 来举例 。
数据结构层面
# models.py
class Author(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Meta:
verbose_name = '作者'
class Book(models.Model):
name = models.CharField(max_length=128)
author = models.ForeignKey(Author,related_name='books',on_delete=models.CASCADE)
def __str__(self):
return self.name
class Meta:
verbose_name = '书籍'
序列化器
class AuthorSerializers(serializers.ModelSerializer):
class Meta:
model = Author
fields = '__all__'
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
depth = 1
def create(self,validated_data,author_id):
author_obj = Author.objects.get(id=author_id)
return Book.objects.create(author=author_obj,**validated_data)
def update(self,instance,validated_data):
# 数据本身
# 序列化后的数据
author_id = validated_data.get('author')
author_obj = Author.objects.filter(id=author_id).first()
book_name = validated_data.get('name')
instance.author = author_obj
instance.name = book_name
return instance.save()
关于上面BookSerializers.create方法、BookSerializers.update方法先不要着急。
视图
为了直观便于理解,我们都使用最为简单的 APIView、Response方便我们具体搞清楚每个请求类型的处理逻辑。实际生产环境建议使用ModelViewSet.
class AuthorView(APIView):
def __init__(self):
self.req = {
'status_code':20000,
'msg':None,
'data':None
}
def get(self, request, *args, **kwargs):
# 请求查询参数
query_set = models.Author.objects.all()
ser = serializers.AuthorSerializers(instance=query_set, many=True)
self.req['data'] = ser.data
return Response(self.req)
def post(self,request,*args,**kwargs):
# 新增请求
ser = serializers.AuthorSerializers(data=request.data)
if ser.is_valid():
ser.save()
self.req['msg'] = 新增成功
else:
self.req['status_code'] = 50000
self.req['msg'] = str(ser.errors)
return Response(self.req)
def put(self,request,*args,**kwargs):
# 更新
name_id = request.data.get('name_id') # 需要修改的编号
name_obj = models.Author.objects.filter(id=name_id).first()
ser = serializers.AuthorSerializers(instance=name_obj,data=request.data)
if ser.is_valid():
ser.save()
self.req['msg'] = '修改成功'
else:
self.req['status_code'] = 50000
self.req['msg'] = str(ser.errors)
return Response(self.req)
class BookView(APIView):
def __init__(self):
self.req = {
'status_code':20000,
'msg':None,
'data':None
}
def get(self, request, *args, **kwargs):
query_set = models.Book.objects.all()
ser = serializers.BookSerializers(instance=query_set, many=True)
self.req['data'] = ser.data
return Response(self.req)
def post(self, request, *args, **kwargs):
ser = serializers.BookSerializers(data=request.data)
if ser.is_valid():
author_id = request.data.get('author')
ser.create(validated_data=ser.validated_data, author_id=author_id)
self.req['msg'] = '新增成功'
else:
self.req['status_code'] = 50000
self.req['msg'] = str(ser.errors)
return Response(self.req)
def put(self,request,*args,**kwargs):
# 更新
books_id = request.data.get('books_id') # 需要更新的id
# 说明这是要更新书籍的名字
books_obj = models.Book.objects.filter(id=books_id).first()
ser = serializers.BookSerializers(instance=books_obj,data=request.data)
if ser.is_valid():
ser.update(books_obj,request.data)
else:
self.req['status_code'] = 50000
self.req['msg'] = str(ser.errors)
return Response(self.req)
个人理解
通过将每个请求拆分开,虽然代码变的一大堆,但是这样方便了理解和阅读,首先是第一个类AuthorView因为他不涉及 一对多,和多对多的问题。
所以在实现新增(post)、修改(put)方法的时候,序列化器基本不需要什么额外的设置,非常简洁就能实现一个类的增、改、查(删自己实现就好了)。
这里只要注意一点
serializer 的参数问题
+ 只传instance为序列化数据,这个时候要注意many告诉是单个还是多个即可;
+ 如果是新增数据就需要传递data参数
+ 如果是修改已有数据instance为现有的数据,data是要更新的数据
不涉及外键的,直接调用save方法是可以直接保存的,不需要自己额外编写,但是需要注意,必须要先过滤一下is_valid()确认数据有效性。否则会报错You must call .is_valid() before calling .save()
接下来是BookView视图,这个就相对复杂一些。他主要涉及了外键问题。所以drf自带的功能没办法满足,我们需要自己定义create和update方法。
由于drf本身并不知道外链的表的问题,所以需要我们通过自己实现。
BookView 的 get请求
这个想展示出外键数据的方法实在太多了,有兴趣的可以去看Django rest_framework 源码学习笔记(一)之序列化器 ,我就直接使用最简单的depth实现了。
BookView 的 post请求
这里就已经设计关联外键的存储问题了,以上面的例子来说就是如何在新增一本书的情况下,让他关联到我指定的作者,所以先实现了ser.create(),并且传了2个参数,第一个是我序列化后的和书籍类相关的参数(ser.validated_data),第二个参数是关联的作者数据(author_id)
在create()方法中,就基本一样了,直接查询对应的数据,然后让他创建返回即可。
BookView 的 put请求
其实这个是最恶心的,特别是一个比较大的表,如果关联很多个外键,那真的是比较恶心的。
首先我先找到了需要修改的书籍类,然后传给update参数,然后在update里面在吧需要更新的外键数据都取出来,分别在去获取到对应的对象模型类,然后在对应的去更新。
以上就是我自己找到的资料和整理出的一些学习结论,虽然更新、创建的方法很多,但是整体来说基本都是这样的一个模式,无非是放在序列化器中还是放在视图中,我个人是比较倾向于放在序列化器中,视图只要放逻辑即可。
在查询了drf的官网后,看到有一句这么说的“If you're supporting writable nested representations you'll need to write .create() or .update() methods that handle saving multiple objects.” 。所以针对这种问题,建议如果不是特别复杂的表,还是用更高级的drf类尽量来减少一些代码吧。