Justin-刘清政的博客

python/Django-rest-framework框架/11-Book系列多表群操作

2020-07-12

Book系列连表接口

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
from django.shortcuts import render

# Create your views here.
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from app01.models import Book
# from app01.ser import BookSerializers
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
# class TestView(APIView):
# def get(self,request):
# 1/0
# return Response({'msg':'个人中心'})
#
# class BookViewSet(ModelViewSet):
# authentication_classes = [BasicAuthentication,]
# queryset = Book.objects.all()
# serializer_class = BookSerializers
# @action(methods=['get'], detail=False)
# def login(self, request):
# Book.objects.update_or_create()
# return Response({'msg':'登陆成功'})
# @action(methods=['put'], detail=True)
# def get_new_5(self, request,pk):
# return Response({'msg':'获取5条数据成功'})

from rest_framework.permissions import AllowAny,IsAuthenticated,IsAdminUser,IsAuthenticatedOrReadOnly



from app01.response import APIResponse
from app01 import models
from app01 import ser as serializers
class PublishAPIView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
publish_obj = models.Publish.objects.filter(pk=pk).first()
if not publish_obj:
return APIResponse(1, 'pk error', http_status=400)
publish_data = serializers.PublishModelSerializer(publish_obj).data
return APIResponse(results=publish_data)

publish_query = models.Publish.objects.all()
return APIResponse(0, 'ok', data=serializers.PublishModelSerializer(publish_query, many=True).data)


class BookAPIView(APIView):
# 单查、群查
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
book_obj = models.Book.objects.filter(pk=pk, is_delete=False).first()
if not book_obj:
return APIResponse(1, 'pk error', http_status=400)
book_data = serializers.BookModelSerializer(book_obj).data
print(book_data)
return APIResponse(data=book_data)

book_query = models.Book.objects.filter(is_delete=False).all()

return APIResponse(0, 'ok', data=serializers.BookModelSerializer(book_query, many=True).data)

# 单删、群删
def delete(self, request, *args, **kwargs):
"""
单删:前台数据为pk,接口为 /books/(pk)/
群删:前台数据为pks,接口为 /books/
"""
pk = kwargs.get('pk')
# 将单删群删逻辑整合
if pk: # /books/(pk)/的接口就不考虑群删,就固定为单删
pks = [pk]
else:
pks = request.data.get('pks')
# 前台数据有误(主要是群删没有提供pks)
if not pks:
return APIResponse(1, 'delete error', http_status=400)
# 只要有操作受影响行,就是删除成功,反之失败
rows = models.Book.objects.filter(is_delete=False, pk__in=pks).update(is_delete=True)
if rows:
return APIResponse(0, 'delete ok')
return APIResponse(1, 'delete failed')

# 单增、群增
def post(self, request, *args, **kwargs):
"""
单增:前台提交字典,接口 /books/
群增:前台提交列表套字典,接口 /books/
"""
request_data = request.data
if isinstance(request_data, dict): # 单增
book_ser = serializers.BookModelSerializer(data=request_data)
if book_ser.is_valid():
book_obj = book_ser.save()
return APIResponse(data=serializers.BookModelSerializer(book_obj).data)
return APIResponse(1, msg=book_ser.errors)
elif isinstance(request_data, list) and len(request_data) != 0 : # 群增
book_ser = serializers.BookModelSerializer(data=request_data, many=True)
book_ser.is_valid(raise_exception=True)
book_obj_list = book_ser.save()
return APIResponse(data=serializers.BookModelSerializer(book_obj_list, many=True).data)
else:
return APIResponse(1, 'data error', http_status=400)

# 单整体改、群整体改
def put(self, request, *args, **kwargs):
"""
单整体改:前台提交字典,接口 /books/(pk)/
群整体改:前台提交列表套字典,接口 /books/,注每一个字典都可以通过pk
"""
pk = kwargs.get('pk')
request_data = request.data
if pk: # 单改
try:
book_obj = models.Book.objects.get(pk=pk)
except:
return APIResponse(1, 'pk error')

# 修改和新增,都需要通过数据,数据依旧给data,修改与新增不同点,instance要被赋值为被修改对象
book_ser = serializers.BookModelSerializer(instance=book_obj, data=request_data)
book_ser.is_valid(raise_exception=True)
book_obj = book_ser.save()
return APIResponse(data=serializers.BookModelSerializer(book_obj).data)
else: # 群改
if not isinstance(request_data, list) or len(request_data) == 0:
return APIResponse(1, 'data error', http_status=400)

# [{pk:1,...}, {pk:3,...}, {pk:100,...}] => [obj1, obj3, obj100] + [{...}, {...}, {...}]
# 要考虑pk对应的对象是否被删,以及pk没有对应的对象
# 假设pk3被删,pk100没有 => [obj1] + [{...}]

# 注:一定不要在循环体中对循环对象进行增删(影响对象长度)的操作
obj_list = []
data_list = []
for dic in request_data:
# request_data可能是list,单内部不一定是dict
try:
pk = dic.pop('pk')
try:
obj = models.Book.objects.get(pk=pk, is_delete=False)
obj_list.append(obj)
data_list.append(dic)
except:
pass
except:
return APIResponse(1, 'data error', http_status=400)

book_ser = serializers.BookModelSerializer(instance=obj_list, data=data_list, many=True)
book_ser.is_valid(raise_exception=True)
book_obj_list = book_ser.save()
return APIResponse(data=serializers.BookModelSerializer(book_obj_list, many=True).data)

# 单局部改、群局部改
def patch(self, request, *args, **kwargs):
"""
单整体改:前台提交字典,接口 /books/(pk)/
群整体改:前台提交列表套字典,接口 /books/,注每一个字典都可以通过pk
"""
pk = kwargs.get('pk')
request_data = request.data
if pk:
try:
book_obj = models.Book.objects.get(pk=pk)
except:
return APIResponse(1, 'pk error')
# 局部修改就是在整体修改基础上设置partial=True,将所有参与反序列化字段设置为required=False
book_ser = serializers.BookModelSerializer(instance=book_obj, data=request_data, partial=True)
book_ser.is_valid(raise_exception=True)
book_obj = book_ser.save()
return APIResponse(data=serializers.BookModelSerializer(book_obj).data)

else: # 群改
if not isinstance(request_data, list) or len(request_data) == 0:
return APIResponse(1, 'data error', http_status=400)

# [{pk:1,...}, {pk:3,...}, {pk:100,...}] => [obj1, obj3, obj100] + [{...}, {...}, {...}]
# 要考虑pk对应的对象是否被删,以及pk没有对应的对象
# 假设pk3被删,pk100没有 => [obj1] + [{...}]

# 注:一定不要在循环体中对循环对象进行增删(影响对象长度)的操作
obj_list = []
data_list = []
for dic in request_data:
# request_data可能是list,单内部不一定是dict
try:
pk = dic.pop('pk')
try:
obj = models.Book.objects.get(pk=pk, is_delete=False)
obj_list.append(obj)
data_list.append(dic)
except:
pass
except:
return APIResponse(1, 'data error', http_status=400)

book_ser = serializers.BookModelSerializer(instance=obj_list, data=data_list, many=True, partial=True)
book_ser.is_valid(raise_exception=True)
book_obj_list = book_ser.save()
return APIResponse(data=serializers.BookModelSerializer(book_obj_list, many=True).data)

class AuthorAPIView(APIView):
def get(self,request,*args,**kwargs):
authors=models.Author.objects.all()
author_ser=serializers.AuthorModelSerializer(authors,many=True)
return APIResponse(data=author_ser.data)
def put(self,reuqest,*args,**kwargs):
pass
def post(self,request,*args,**kwargs):
author_ser=serializers.AuthorModelSerializer(data=request.data)
author_ser.is_valid(raise_exception=True)
author_ser.save()
return APIResponse()
def delete(self,request,*args,**kwargs):
pass

ser.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82



from rest_framework import serializers
from app01 import models
class BookListSerializer(serializers.ListSerializer):
# 1、create方法父级ListSerializer已经提供了
# def create(self, validated_data):
# # 通过self.child来访问绑定的ModelSerializer
# print(self.child)
# raise Exception('我不提供')

# 2、父级ListSerializer没有通过update方法的实现体,需要自己重写
def update(self, instance, validated_data):
# print(instance)
# print(validated_data)
return [
self.child.update(instance[i], attrs) for i, attrs in enumerate(validated_data)
]



class BookModelSerializer(serializers.ModelSerializer):
# 通过BookModelSerializer.Meta.list_serializer_class来访问绑定的ListSerializer
class Meta:
# 关联ListSerializer完成群增群改
list_serializer_class = BookListSerializer

model = models.Book
# fields = ('name', 'price', 'publish', 'authors')
# fields = ('name', 'price', 'publish_name', 'author_list')

# 了解
# fields = '__all__'
# exclude = ('id', )
# depth = 1

# 序列化与反序列化整合
fields = ('name', 'price', 'publish_name', 'author_list', 'publish', 'authors')
extra_kwargs = {
'publish': {
'write_only': True
},
'authors': {
'write_only': True
}
}




# 前提:如果只有查需求的接口,自定义深度还可以用子序列化方式完成
class PublishModelSerializer(serializers.ModelSerializer):
# 子序列化都是提供给外键(正向方向)完成深度查询的,外键数据是唯一:many=False;不唯一:many=True
# 注:只能参与序列化,且反序列化不能写(反序列化外键字段会抛异常)
books = BookModelSerializer(many=True)
class Meta:
model = models.Publish
fields = ('name', 'address', 'books')

class AuthorModelSerializer(serializers.ModelSerializer):
class Meta:
model=models.Author
fields=('name','sex','mobile','mobile_in')
extra_kwargs={
'mobile':{
'read_only': True
},
}

mobile_in=serializers.CharField(write_only=True)
# def validate_mobile_in(self, data):
# print(data)
# return data


def create(self, validated_data):
print(validated_data)
mobile=validated_data.pop('mobile_in')
author=models.Author.objects.create(**validated_data)
authordetail=models.AuthorDetail.objects.create(mobile=mobile,author=author)
return author

models.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
from django.db import models



# 一、基表
# Model类的内部配置Meta类要设置abstract=True,这样的Model类就是用来作为基表

# 多表:Book,Publish,Author,AuthorDetail
class BaseModel(models.Model):
is_delete = models.BooleanField(default=False)
create_time = models.DateTimeField(auto_now_add=True)
class Meta:
# 基表必须设置abstract,基表就是给普通Model类继承使用的,设置了abstract就不会完成数据库迁移完成建表
abstract = True

class Book(BaseModel):
name = models.CharField(max_length=16)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING)
# 重点:多对多外键实际在关系表中,ORM默认关系表中两个外键都是级联
# ManyToManyField字段不提供设置on_delete,如果想设置关系表级联,只能手动定义关系表
authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)

# 自定义连表深度,不需要反序列化,因为自定义插拔属性不参与反序列化
@property
def publish_name(self):
return self.publish.name
@property
def author_list(self):
temp_author_list = []
for author in self.authors.all():
temp_author_list.append({
'name': author.name,
'sex': author.get_sex_display(),
'mobile': author.detail.mobile
})
return temp_author_list


class Publish(BaseModel):
name = models.CharField(max_length=16)
address = models.CharField(max_length=64)

class Author(BaseModel):
name = models.CharField(max_length=16)
sex = models.IntegerField(choices=[(0, '男'),(1, '女')], default=0)

class AuthorDetail(BaseModel):
mobile = models.CharField(max_length=11)
# 有作者可以没有详情,删除作者,详情一定会被级联删除
# 外键字段为正向查询字段,related_name是反向查询字段
author = models.OneToOneField(to='Author', related_name='detail', db_constraint=False, on_delete=models.CASCADE)

# 二、表断关联
# 1、表之间没有外键关联,但是有外键逻辑关联(有充当外键的字段)
# 2、断关联后不会影响数据库查询效率,但是会极大提高数据库增删改效率(不影响增删改查操作)
# 3、断关联一定要通过逻辑保证表之间数据的安全
# 4、断关联
# 5、级联关系
# 作者没了,详情也没:on_delete=models.CASCADE
# 出版社没了,书还是那个出版社出版:on_delete=models.DO_NOTHING
# 部门没了,员工没有部门(空不能):null=True, on_delete=models.SET_NULL
# 部门没了,员工进入默认部门(默认值):default=0, on_delete=models.SET_DEFAULT


# 三、ORM外键设计
# 1、一对多:外键放在多的一方
# 2、多对多:外键放在常用的一方
# 3、一对一:外键放在不常用的一方
# 4、外键字段为正向查询字段,related_name是反向查询字段


# from django.contrib.auth.models import AbstractUser, User
# class MyUser(AbstractUser):
# pass

setting.py

1
2
3
4
5
6
7
8
9
LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = False

urls.py

1
2
3
4
5
path(r'publishes/', views.PublishAPIView.as_view()),
re_path(r'^publishes/(?P<pk>\d+)/$', views.PublishAPIView.as_view()),

path(r'books/', views.BookAPIView.as_view()),
re_path(r'^books/(?P<pk>\d+)/$', views.BookAPIView.as_view()),
使用支付宝打赏
使用微信打赏

点击上方按钮,请我喝杯咖啡!

扫描二维码,分享此文章