0.介绍
a. 本文介绍了常用的,视图&反序列化&序列化,中使用的方法。
b. 序列化器他有两个作用:序列化、校验。
c. 序列化、校验,可以走一个序列化器,也可以走两个。
1. 视图
1.1 选择serializer
class TestView(ListAPIView,CreateAPIView):
queryset = models.Topic.objects
serializer_class = TestSER
def get_serializer_class(self):
# self.auction == 'list'
if self.request.method == "GET":
return TestSER
return TestSER2
1.2 选择authenticator
权限频率都是一样的使用,都是做判断,返回一个对象列表即可
class CommentAPIView(APIView):
"""获取更多评论"""
def get_authenticators(self):
# 重写认证类
if self.request.method == 'POST':
return [LoginAuthentication(), ]
return [PublicAuthentication(), ]
def get(self, request, *args, **kwargs):
"""获取更多评论,通过root"""
# print('1', request.user)
root = int(request.query_params.get('root'))
# print(root)
queryset = models.Comment.objects.filter(root=root)
# print(queryset)
ser = CommentModelSerializer(instance=queryset, many=True)
return Response(data=ser.data)
def post(self, request, *args, **kwargs):
"""保存评论数据"""
ser = PostCommentModelSerializer(data=request.data) # ...
# print(request.data)
if ser.is_valid():
ser.save(userinfo_id=request.user)
# 评论数+1
new_id = ser.validated_data.get('new').id
models.Release.objects.filter(id=new_id).update(release_num=F('release_num') + 1)
return Response(ser.data, status=status.HTTP_201_CREATED)
return Response(ser.errors, status=status.HTTP_400_BAD_REQUEST)
1.3 自定义视图函数
比如:继承ListAPIView重写list方法,
继承RetrieveAPIView重写list方法retrieve
class AuctionItemDetailView(RetrieveAPIView):
"""拍品详细,前端传过来,相应的商品id"""
queryset = models.AuctionItem.objects.filter(status__gt=1)
serializer_class = AuctionItemDetailModelSerializer
def retrieve(self, request, *args, **kwargs):
response = super(AuctionItemDetailView, self).retrieve(request, *args, **kwargs)
# 路由的pk可以从kwargs中取
pk = kwargs.get('pk')
user_obj = request.user
if user_obj:
models.BrowseRecord.objects.get_or_create(user=user_obj, item_id=pk)
# 商品记录加一
models.AuctionItem.objects.filter(pk=pk).update(look_count=F('look_count') + 1)
return response
1.4 自定义filter做筛选
1.5 perform_create(用户自己传入一些数据)
# 用户自己传入一些数据
# 与重写create不同的是,perform_create,使用默认的create,但可以自己传入数据
# 案例
class CommentAPIView(ListAPIView, CreateAPIView):
"""获取更多评论&&保存评论视图"""
queryset = models.Comment.objects.all()
serializer_class = TextSerializer
def perform_create(self, serializer):
# 执行创建传入默认的数据
serializer.save(userinfo=self.request.user)
1.5 重写视图/添加过滤条件/create之前
class AuctionBidView(ListAPIView, CreateAPIView):
"""竞价 www.xxxx.com/../../?item_id=1"""
authentication_classes = [LoginAuthentication, ]
queryset = models.BidRecord.objects.filter(status=1)
serializer_class = BidRecordModelSerializer
def get_queryset(self):
# 进行过滤,找到当前出价的商品
item_id = self.request.query_params.get('item_id')
queryset = self.queryset.filter(item_id=item_id)
return queryset
def get(self, request, *args, **kwargs):
# 重写get,构造返回的数据
response = super(AuctionBidView, self).get(request, *args, **kwargs)
# 序列化之后的数据
# [OrderedDict([('id', 1), ('status', '竞价'), ('item', 2), ('user', 1)]), OrderedDict([('id', 2), ('status', '竞价'), ('item', 2), ('user', 1)])]
# print(response.data)
item_id = self.request.query_params.get('item_id')
item_obj = models.AuctionItem.objects.filter(id=item_id).first()
# 取最大价格,如果没有,取低价
# 进行聚合,求出最大值,{'max_price':1200}
max_price = models.BidRecord.objects.filter(status=1, item_id=item_id).aggregate(max_price=Max('price'))[
'max_price']
data = {
'unit': item_obj.unit,
'price': max_price or item_obj.start_price,
'result': response.data
}
response.data = data
return response
def perform_create(self, serializer):
# 用户自己添加数据
# 添加锁
from django.db import transaction
from rest_framework.exceptions import ValidationError
with transaction.atomic():
item_id = self.request.data.get('item_id')
price = self.request.data.get('price')
# 加锁,同一个价格只能一个出价 select_for_update
result = models.BidRecord.objects.filter(status=1, item_id=item_id).aggregate(
max_price=Max('price')).select_for_update()
max_price = result.get('max_price')
if price < max_price:
raise ValidationError('价格不够!')
serializer.save(user=self.request.user)
1.6 重写视图,拿到序列化好的字段,进行修改
1.6.1 案例一
def get(self, request, *args, **kwargs):
# 重写get,构造返回的数据
response = super(AuctionBidView, self).get(request, *args, **kwargs)
# 序列化之后的数据
# [OrderedDict([('id', 1), ('status', '竞价'), ('item', 2), ('user', 1)]), OrderedDict([('id', 2), ('status', '竞价'), ('item', 2), ('user', 1)])]
# print(response.data)
item_id = self.request.query_params.get('item_id')
item_obj = models.AuctionItem.objects.filter(id=item_id).first()
# 取最大价格,如果没有,取低价
# 进行聚合,求出最大值,{'max_price':1200}
max_price = models.BidRecord.objects.filter(status=1, item_id=item_id).aggregate(max_price=Max('price'))[
'max_price']
data = {
'unit': item_obj.unit,
'price': max_price or item_obj.start_price,
'result': response.data
}
response.data = data
return response
1.6.2 案例二
class CouponUserView(ListAPIView, CreateAPIView):
"""用户的优惠劵"""
authentication_classes = [LoginAuthentication, ]
serializer_class = CouponUserSerializer
def get_queryset(self):
queryset = models.UserCoupon.objects.filter(user=self.request.user, is_delete=False, is_show=True).order_by(
'-id')
return queryset
def list(self, request, *args, **kwargs):
response = super(CouponUserView, self).list(request, *args, **kwargs)
# 如果有问题,报错
if response.status_code != status.HTTP_200_OK:
return response
# 更改返回的格式 {1:{'text':'未使用',child:[] }}
# 有序字典
status_dict = OrderedDict()
for item in models.UserCoupon.status_type:
status_dict[item[0]] = {'text': item[1], 'child': []}
for row in response.data:
# print(row)
status_dict[int(row['status'])]['child'].append(row)
response.data = status_dict
return response
2. 反序列化
2.1 普通校验
class TestSER(serializers.ModelSerializer):
class Meta:
model = models.Topic
fields = "__all__"
2.2 自定义校验规则---validators
import re
from rest_framework.exceptions import ValidationError
def phone_validator(value):
if not re.match(r"^(1[3|4|5|6|7|8|9])\d{9}$",value):
raise ValidationError('手机格式错误')
class TestSER(serializers.ModelSerializer):
title = serializers.CharField(label='手机号',validators=[phone_validator,])
class Meta:
model = models.Topic
fields = "__all__"
2.3 自定义方法校验---全局/局部钩子
class TestSER(serializers.ModelSerializer):
title = serializers.CharField(label='手机号')
class Meta:
model = models.Topic
fields = "__all__"
def validate_title(self, value):
# 具体的校验规则
return value
2.4 序列化嵌套(反序列化) 一般前端传送列表套字典,都是用该方法
url
path('release/', ReleaseAPIView.as_view()),
view
from rest_framework.views import APIView
from utils.centent.cos import credential
from rest_framework.response import Response
from rest_framework.generics import CreateAPIView
from api.serializer import business
class ReleaseAPIView(CreateAPIView):
"""发布保存视图"""
serializer_class = business.ReleaseModelSerializer
def get_queryset(self):
# 重写queryset
queryset = models.Release.objects.filter(is_delete=False, is_show=True)
return queryset
def post(self, request, *args, **kwargs):
# 重写post
response = super(ReleaseAPIView, self).post(request, *args, **kwargs)
# 进行自定义返回数据
response.data = ''
return response
def perform_create(self, serializer):
"""在保存之前为对应的topic人数+1"""
print("serializer", serializer.data)
# serializer {'position': 'xx', 'content': 'xx', 'userinfo': 2, 'topic': 1, 'imageList': [OrderedDict([('cos_url', 'xx'), ('cos_media_name', 'xx')]), OrderedDict([('cos_url', 'xx'), ('cos_media_name', 'xx')])]}
print("serializer", serializer.data.get('topic'))
# serializer 1
ser
from rest_framework import serializers
from api import models
class MediaModelSerializer(serializers.ModelSerializer):
# 用作序列化器嵌套
class Meta:
model = models.Media
fields = ['cos_url', 'cos_media_name']
class ReleaseModelSerializer(serializers.ModelSerializer):
"""发布保存序列化器"""
# imageList默认会序列化,必须加一个write_only,不然报错
imageList = MediaModelSerializer(many=True, write_only=True)
class Meta:
model = models.Release
fields = ['position', 'content', 'userinfo', 'topic', 'imageList']
def validate(self, attrs):
imageList = attrs.get('imageList')
# print(imageList) # [OrderedDict([('cos_url', 'xx'), ('cos_media_name', 'xx')]), OrderedDict([('cos_url', 'xx'), ('cos_media_name', 'xx')])]
return attrs
2.5 某个字段,反序列化不进行校验,序列化的时候忽略
class TestSER(serializers.ModelSerializer):
# 序列化忽略
name = serializers.CharField(label='手机号',wirte_only=True)
# 反序列化不进行校验
title = serializers.CharField(label='手机号',read_only=True)
class Meta:
model = models.Topic
fields = "__all__"
2.6 ps 关于write_only/read_only 详解
3. 序列化
3.1 普通序列化
class TestSER(serializers.ModelSerializer):
class Meta:
model = models.Topic
fields = "__all__"
3.2 自定义字段+source
class TestSER(serializers.ModelSerializer):
v1 = serializers.CharField(label='手机号',source='get_gender_display')
class Meta:
model = models.Topic
fields = "__all__"
3.3 自定义时间字段
class TestSER(serializers.ModelSerializer):
v1 = serializers.DateTimeField(format="%Y")
class Meta:
model = models.Topic
fields = "__all__"
3.4 自定义返回格式---get_xxx
from django.forms.models import model_to_dict
class NewsDetailModelSerializer(serializers.ModelSerializer):
images = serializers.SerializerMethodField()
create_date = serializers.DateTimeField(format="%Y-%m-%d %H:%M")
user = serializers.SerializerMethodField()
topic = serializers.SerializerMethodField()
viewer = serializers.SerializerMethodField()
comment = serializers.SerializerMethodField()
is_favor = serializers.SerializerMethodField()
class Meta:
model = models.News
exclude = ['cover',]
def get_images(self,obj):
detail_queryset = models.NewsDetail.objects.filter(news=obj)
# return [row.cos_path for row in detail_queryset]
# return [{'id':row.id,'path':row.cos_path} for row in detail_queryset]
return [model_to_dict(row,['id','cos_path']) for row in detail_queryset]
def get_user(self, obj):
return model_to_dict(obj.user, fields=['id', 'nickname', 'avatar'])
def get_topic(self, obj):
if not obj.topic:
return
return model_to_dict(obj.topic, fields=['id', 'title'])
def get_viewer(self,obj):
# 根据新闻的对象 obj(news)
# viewer_queryset = models.ViewerRecord.objects.filter(news_id=obj.id).order_by('-id')[0:10]
queryset = models.ViewerRecord.objects.filter(news_id=obj.id)
viewer_object_list = queryset.order_by('-id')[0:10]
context = {
'count':queryset.count(),
'result': [model_to_dict(row.user,['nickname','avatar']) for row in viewer_object_list]
}
return context
def get_comment(self,obj):
"""
获取所有的1级评论,再给每个1级评论获取一个耳机评论。
:param obj:
:return:
"""
# 1.获取所有的 一级 评论
first_queryset = models.CommentRecord.objects.filter(news=obj,depth=1).order_by('id')[0:10].values(
'id',
'content',
'depth',
'user__nickname',
'user__avatar',
'create_date'
)
first_id_list = [ item['id'] for item in first_queryset]
# 2.获取所有的二级评论
# second_queryset = models.CommentRecord.objects.filter(news=obj,depth=2)
# 2. 获取所有1级评论下的二级评论
# second_queryset = models.CommentRecord.objects.filter(news=obj, depth=2,reply_id__in=first_id_list)
# 2. 获取所有1级评论下的二级评论(每个二级评论只取最新的一条)
from django.db.models import Max
result = models.CommentRecord.objects.filter(news=obj, depth=2, reply_id__in=first_id_list).values('reply_id').annotate(max_id=Max('id'))
second_id_list = [item['max_id'] for item in result] # 5, 8
second_queryset = models.CommentRecord.objects.filter(id__in=second_id_list).values(
'id',
'content',
'depth',
'user__nickname',
'user__avatar',
'create_date',
'reply_id',
'reply__user__nickname'
)
import collections
first_dict = collections.OrderedDict()
for item in first_queryset:
item['create_date'] = item['create_date'].strftime('%Y-%m-%d')
first_dict[item['id']] = item
for node in second_queryset:
first_dict[node['reply_id']]['child'] = [node,]
return first_dict.values()
def get_is_favor(self,obj):
# 1. 用户未登录
user_object = self.context['request'].user
if not user_object:
return False
# 2. 用户已登录
exists = models.NewsFavorRecord.objects.filter(user=user_object,news=obj).exists()
return exists
3.5 自定义返回数据格式---models添加方法
class Release(BaseModel):
"""发布"""
position = models.CharField(verbose_name='位置', max_length=225, blank=True, null=True)
content = models.CharField(verbose_name='内容', max_length=225)
# 这三个存数量,做一个优化
release_up = models.BigIntegerField(verbose_name='新闻点赞数量', default=0)
watch_num = models.BigIntegerField(verbose_name='观看人数数量', default=0)
release_num = models.BigIntegerField(verbose_name='评论数量', default=0)
# db_constraint=False db_constraint=False 逻辑上的关联,实质上没有外键联系,增删不会受外键影响,orm查询不影响
userinfo = models.ForeignKey(verbose_name='用户', to='UserInfo', on_delete=models.DO_NOTHING, db_constraint=False)
topic = models.ForeignKey(verbose_name='话题', to='Topic', on_delete=models.DO_NOTHING, db_constraint=False,
null=True, blank=True, related_name='topic_release')
# 返回图片
@property
def cover_msg_url(self):
# 每一个对象走一遍
msg_url = self.media.all().filter(is_show=True, is_delete=False).first().cosUrl
# print(msg_url)
return msg_url
################序列化器####################
class ReleaseModelSerializer(serializers.ModelSerializer):
"""话题序列化器"""
class Meta:
model = models.Release
fields = ['id', 'cover_msg_url']
3.6 1.6 在序列化器里面使用序列化器
class AuctionItem(serializers.ModelSerializer):
"""拍卖场详细--拍品 序列化器嵌套"""
is_deposit = serializers.SerializerMethodField()
class Meta:
model = models.AuctionItem
fields = ['id', 'uid', 'title', 'cover', 'is_deposit',
'start_price', 'reserve_price', 'highest_price',
'unit',
]
# 是否缴纳单品保证金
# 是否缴纳全场/单品保证金
def get_is_deposit(self, obj):
# 判断是否登录
user = self.context['request'].user
if not user:
return False
# 如果是全场,单品为null
# 判断是否交了保障金,True/False
exist = models.DepositRecord.objects.filter(user=user, item=obj, status=2).exists()
return exist
class AuctionDetailModelSerializer(serializers.ModelSerializer):
"""拍卖场详细"""
status = serializers.CharField(source='get_status_display')
auction_item = serializers.SerializerMethodField()
preview_start_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
preview_end_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
auction_start_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
auction_end_time = serializers.DateTimeField(format='%Y-%m-%d %H:%M', read_only=True)
is_deposit = serializers.SerializerMethodField()
class Meta:
model = models.Auction
fields = [
'id', 'status', 'title', 'cover', 'is_deposit', 'preview_start_time',
'preview_end_time', 'auction_start_time', 'auction_end_time',
'look_count', 'auction_item'
]
def get_auction_item(self, obj):
# 查询拍卖品
item_list = models.AuctionItem.objects.filter(auction=obj)
# 把request传入上一个序列化器,序列化器里面使用序列化器
ser = AuctionItem(instance=item_list, many=True, context=self.context)
return ser.data
# 是否缴纳全场/单品保证金
def get_is_deposit(self, obj):
# 判断是否登录
user = self.context['request'].user
if not user:
return False
# 如果是全场,单品为null
# 判断是否交了保障金,True/False
exist = models.DepositRecord.objects.filter(user=user, item__isnull=True, status=2).exists()
return exist
4. 序列化的方法中获取request||context存值
class TestSER(serializers.ModelSerializer):
title = serializers.SerializerMethodField()
class Meta:
model = models.Topic
fields = "__all__"
def get_title(self,obj):
request = self.context['request']
# serializer放值
# ser
def _give_data(self, user_obj, token):
"""
在context赋值,用于返回前端
"""
self.context['user_id'] = user_obj.id
self.context['username'] = user_obj.username
self.context['phone'] = user_obj.phone
self.context['avatar'] = user_obj.avatar
self.context['bucket'] = user_obj.bucket
self.context['region'] = user_obj.region
self.context['balance'] = user_obj.balance
self.context['token'] = token
# view
from rest_framework import status
from rest_framework.response import Response
from rest_framework.generics import RetrieveAPIView, CreateAPIView
from api.serializer.account import LoginRegisterModelSerializer
class LoginRegisterAPIView(RetrieveAPIView, CreateAPIView):
"""登录注册视图"""
# post请求进行
authentication_classes = []
def post(self, request, *args, **kwargs):
ser = LoginRegisterModelSerializer(data=request.data)
if ser.is_valid():
# 获取数据返回
result = {
"status": 1000,
"data": {
"user_id": ser.context.get('user_id'),
"username": ser.context.get('username'),
"phone": ser.context.get('phone'),
"avatar": ser.context.get('avatar'),
"bucket": ser.context.get('bucket'),
"region": ser.context.get('region'),
"balance": ser.context.get('balance'),
"token": ser.context.get('token'),
}
}
return Response(data=result, status=status.HTTP_200_OK)
error = ser.errors
return Response(data=error, status=status.HTTP_400_BAD_REQUEST)
5. 序列化器里面使用序列化器,如果最上面的,想要使用request的话,是下面的序列化器,传过去的
ser = AuctionItem(instance=item_list, many=True, context=self.context)
6. 在局部钩子里面拿到其他的字段
def validate_price(self, attrs):
# initial_data--在局部钩子里面,获取其他的字段
item_id = self.initial_data.get('item')
return attrs
7. 聚合
class BidRecordModelSerializer(serializers.ModelSerializer):
status = serializers.CharField(source='get_status_display', read_only=True)
username = serializers.CharField(source='user.nickName', read_only=True)
id = serializers.CharField(read_only=True)
class Meta:
model = models.BidRecord
fields = ['id', 'status', 'item', 'price', 'username']
def validate_item(self, attrs):
# item传的就是id,但被反射成立对象
exist = models.AuctionItem.objects.filter(id=attrs.id, status=3).exists()
if not exist:
raise serializers.ValidationError('当前商品未开始竞拍,请等待!')
def validate_price(self, attrs):
# 底价/最高价/加价幅度 item---这里是id
item_id = self.initial_data.get('item')
auction_obj = models.AuctionItem.objects.filter(id=item_id).first()
if int(attrs) < auction_obj.start_price:
raise serializers.ValidationError('价格必须比低价高!')
# 1. 去出价表,查出最新最大的一条价格记录
# 2. 当前价格与查询的价格进行比较,
from django.db.models import Max
# 获取前端传送改过来的item---initial_data# 进行聚合,求出最大值,{'max_price':1200}
old_price = models.BidRecord.objects.filter(status=1, item_id=item_id).aggregate(max_price=Max('price'))[
'max_price']
# 4. 如果比查询出来的小,异常
if int(attrs) < old_price:
raise serializers.ValidationError('出价不能比当前价格小!')
# 3. 如果不是加价幅度,异常
if int(attrs) % auction_obj.unit != 0:
raise serializers.ValidationError('请按照加价幅度出价!')
return attrs
8. 分组
from django.db.models import Max
# 每条二级评论的id的最大值
# <QuerySet [{'reply_id': 7, 'max_id': 15}, {'reply_id': 8, 'max_id': 11}, {'reply_id': 9, 'max_id': 12}]>
result = models.Comment.objects.filter(is_show=True, is_delete=False, depth=2,
reply_id__in=first_id_list).values('reply_id').annotate(
max_id=Max('id'))