DRF-序列器应用
项目所需表结构
点击展开
from django.db import models
class UserInfo(models.Model):
username = models.CharField(verbose_name="用户名", max_length=32, db_index=True)
password = models.CharField(verbose_name="密码", max_length=64)
token = models.CharField(verbose_name="TOKEN", max_length=64, null=True, blank=True,db_index=True)
class Blog(models.Model):
category_choices = ((1, "云计算"), (2, "Python全栈"), (3, "Go开发"))
category = models.IntegerField(verbose_name="分类", choices=category_choices)
image = models.CharField(verbose_name="封面", max_length=255)
title = models.CharField(verbose_name="标题", max_length=32)
summary = models.CharField(verbose_name="简介", max_length=256)
text = models.TextField(verbose_name="博文")
ctime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
creator = models.ForeignKey(verbose_name="创建者", to="UserInfo", on_delete=models.CASCADE)
comment_count = models.PositiveIntegerField(verbose_name="评论数", default=0)
favor_count = models.PositiveIntegerField(verbose_name="赞数", default=0)
class Favor(models.Model):
""" 赞 """
blog = models.ForeignKey(verbose_name="博客", to="Blog", on_delete=models.CASCADE)
user = models.ForeignKey(verbose_name="用户", to="UserInfo", on_delete=models.CASCADE)
create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=['blog', 'user'], name='uni_favor_blog_user')
]
class Comment(models.Model):
""" 评论表 """
blog = models.ForeignKey(verbose_name="博客", to="Blog", on_delete=models.CASCADE)
user = models.ForeignKey(verbose_name="用户", to="UserInfo", on_delete=models.CASCADE)
content = models.CharField(verbose_name="内容", max_length=150)
create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
- 定义一个路由,用来创建数据库信息
path('db/', views.db)
创建用户信息,及对应博客信息
def db(request):
models.UserInfo.objects.create(username='foo1', password='foo1')
models.UserInfo.objects.create(username='foo2', password='foo2')
models.Blog.objects.create(
category=1, image='xx/xx.jpg', title='标题1', summary='简介1', text='博文1', creator_id=1)
models.Blog.objects.create(
category=2, image='xx/xx.jpg', title='标题2', summary='简介2', text='博文2', creator_id=2
)
# 后续在这里添加项目所需数据
return HttpResponse('ok')
- 展示所有用户信息
path('api/blog/', views.BlogView.as_view())
class BlogSerializer(serializers.ModelSerializer):
category = serializers.CharField(source='get_category_display')
ctime = serializers.DateTimeField(format='%Y-%m-%d')
# blog表里creator 是一个外加按字段,这里定义钩子方法,以展示字段对应的username
creator = serializers.SerializerMethodField()
class Meta:
model = models.Blog
fields = ['id', 'category', 'title', 'summary', 'ctime', 'creator', 'comment_count', 'favor_count']
def get_creator(self, obj):
return {'id': obj.creator.id, 'username': obj.creator.username}
class BlogView(APIView):
def get(self, request, *args, **kwargs):
"""博客列表"""
queryset = models.Blog.objects.all().order_by('-id')
ser = BlogSerializer(instance=queryset, many=True)
return Response(ser.data)
- 博客详细信息展示
path('api/blog/<int:pk>', views.BlogDetailView.as_view())
在上个url基础上在末尾加上blog的id,得到博客的详细信息
class BlogDetailSerializer(serializers.ModelSerializer):
category = serializers.CharField(source='get_category_display')
ctime = serializers.DateTimeField(format='%Y-%m-%d')
creator = serializers.SerializerMethodField()
# comments = serializers.SerializerMethodField()
class Meta:
model = models.Blog
fields = '__all__'
def get_creator(self, obj):
return {'id': obj.creator.id, 'username': obj.creator.username}
class BlogDetailView(APIView):
def get(self, request, *args, **kwargs):
"""博客列表"""
pk = kwargs.get('pk')
queryset = models.Blog.objects.filter(id=pk).first()
if not queryset:
return Response({'error': '博客不存在'})
ser = BlogDetailSerializer(instance=queryset)
return Response(ser.data)

4.展示博客对应的所有评论
path('api/comment/<int:blog_id>', views.CommentView.as_view())
这里编写序列化类时继承了父类HookSerializer,基于源码改写Serializer的to_representation方法,
一个字段既需要进行数据校验又需要做序列化返回,并且这个返回值需要我们对数据库的字段值进行再加工,在类里加get_字段名即可,代码如下,可以直接使用:
from rest_framework.fields import SkipField
from rest_framework.relations import PKOnlyObject
class HookSerializer:
def to_representation(self, instance):
"""
Object instance -> Dict of primitive datatypes.
"""
ret = {}
fields = self._readable_fields
for field in fields:
if hasattr(self, 'get_%s' % field.field_name):
value = getattr(self, 'get_%s' % field.field_name)(instance)
ret[field.field_name] = value
else:
try:
attribute = field.get_attribute(instance)
except SkipField:
continue
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
if check_for_none is None:
ret[field.field_name] = None
else:
ret[field.field_name] = field.to_representation(attribute)
return ret
class CommentSerializer(HookSerializer, serializers.ModelSerializer):
class Meta:
model = models.Comment
fields = ['id', 'content', 'user', 'blog', 'create_datetime']
def get_user(self, obj):
return obj.user.username
class CommentView(APIView):
def get(self, request, blog_id):
"""评论列表"""
queryset = models.Comment.objects.filter(blog_id=blog_id)
ser = CommentSerializer(instance=queryset, many=True)
context = {'code': 200, 'data': ser.data}
return Response(context)
结果:

5.注册功能
path('api/register/', views.RegisterView.as_view())
用户提交的数据应符合{'username':'foo2', 'password':'foo2','confirm_password':'foo2'}
所以
在序列化类中,定义confirm_password字段,并在钩子函数里校验密码是否一致
在视图函数中,我们要做的是:数据校验+数据保存+返回序列化信息
class RegisterSerializer(serializers.ModelSerializer):
# write_only=True,只做数据校验,不做序列化
confirm_password = serializers.CharField(write_only=True)
class Meta:
model = models.UserInfo
fields = ['username', 'password', 'confirm_password']
extra_kwargs = {
'id': {'read_only': True},
'password': {'write_only': True},
}
def validate_confirm_password(self, value):
# 在这里用的是initial_data,字典里包含了用户传入的所有信息
# 在confirm_password的钩子函数里获取password值,必须确保fileds里password在confirm_password之前
password = self.initial_data.get('password')
if value != password:
raise serializers.ValidationError('两次密码不一致')
return value
class RegisterView(APIView):
def post(self, request):
"""注册"""
ser = RegisterSerializer(data=request.data)
# 校验失败,返回错误信息
# 校验成功,保存数据并返回注册的用户信息
if ser.is_valid():
# 保存之前删除confirm字段
ser.validated_data.pop('confirm_password')
ser.save()
return Response({'code': 1000, 'data': ser.data})
return Response({'code': 1001, 'error': '注册失败', 'detail': ser.errors})
密码不一致或者没传某个字段信息都会抛出错误

校验成功

6. 登录
path('api/login/', views.LoginView.as_view())
- 用户传入数据
- 数据校验,不通过返回错误信息
- 校验通过生成token并返回给用户(序列化)
class LoginSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = ['username', 'password']
class LoginView(APIView):
def post(self, request):
ser = LoginSerializer(data=request.data)
if not ser.is_valid():
return Response({'code': 1001, 'error': '校验失败', 'detail': ser.errors})
user = models.UserInfo.objects.filter(**ser.validated_data).first()
if not user:
return Response({'code': 1002, 'error': '用户名或密码错误'})
user.token = str(uuid.uuid4())
user.save()
return Response({'code': 1000, 'info': {'token': user.token}})
7.创建评论
- 认证组件-登录成功才能访问 可以在之前写过的
CommentView里添加post请求对应的视图函数,添加评论
问题:但是这样会有一个问题,得到博客评论信息是不需要登录的,但是添加评论需要登录,一旦在settings或者视图类里添加认证组件,就意味着两种请求都需要认证。
解决:修改认证组件逻辑,如果认证不成功,就让request.user和request.data等于默认值,去掉认证不通过抛出错误的逻辑,这样,get请求即使认证不通过也能访问,post请求判断他们的值是否存在,不存在就表示校验不通过
- 编写认证类
from rest_framework.authentication import BaseAuthentication
from api import models
# 从URL里获取
# 访问方式:http://127.0.0.1:8000/api/comment/1?token=edf02708-bfb7-459e-91f4-7aa13daca165
class M1Authentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get('token')
if not token:
return None
user_obj = models.UserInfo.objects.filter(token=token).first()
if not user_obj:
return None
return user_obj, token
# 从请求头中获取
# Authorization: token
class M2Authentication(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_AUTHORIZATION', '')
if not token:
return None, None
user_obj = models.UserInfo.objects.filter(token=token).first()
if not user_obj:
return None, None
return user_obj, token
- 编写视图类
我改写了之前写好的序列化类CommentSerializer,有些字段只做序列化,有些字段只做数据校验
class CommentSerializer(HookSerializer, serializers.ModelSerializer):
class Meta:
model = models.Comment
fields = ['id', 'content', 'user', 'blog', 'create_datetime']
extra_kwargs = {
'id': {'read_only': True},
"user": {"read_only": True},
"blog": {"read_only": True},
"create_datetime": {"read_only": True},
}
def get_user(self, obj):
return obj.user.username
class CommentView(APIView):
authentication_classes = [M1Authentication, M2Authentication]
def get(self, request, blog_id):
"""评论列表"""
...
def post(self, request, blog_id):
"""发表评论"""
user_obj = request.user
# 评论之前先判断用户是否登录
if not user_obj:
return Response({'code': 1001, 'error': '请先登录'})
blog_obj = models.Blog.objects.filter(id=blog_id).first()
if not blog_obj:
return Response({'code': 1002, 'error': '博客不存在'})
ser = CommentSerializer(data=request.data)
# 判断用户是否传递了content字段
if not ser.is_valid():
return Response({'code': 1001, 'error': '校验失败', 'detail': ser.errors})
# post请求的数据{"content": "hh"}
# 此时的`ser.validated_data`: {'content': 'xxx'}
# 保存数据之前添加必填字段
ser.save(blog=blog_obj, user=user_obj)
return Response({'code': 1000, 'data': ser.data})

8. 点赞
class FavorSerializer(serializers.ModelSerializer):
class Meta:
model = models.Favor
fields = ['blog', 'id']
class FavorView(APIView):
authentication_classes = [M1Authentication, M2Authentication, NoAuthentication]
def post(self, request):
# 认证不成功,执行到这一步之前抛出错误
# 走到这一步一定认证成功
ser = FavorSerializer(data=request.data)
# 是否传递预期值 如:{'blog': 1}
if not ser.is_valid():
return Response({'code': 1001, 'error': '校验失败', 'detail': ser.errors})
# 是否已经点过赞
blog = ser.validated_data.get('blog')
exits = models.Favor.objects.filter(blog=blog, user=request.user).exists()
if exits:
return Response({'code': 1002, 'error': '已经点赞过了!'})
# 保存数据
ser.save(user=request.user)
return Response({'code': 1000, 'data': ser.data, 'detail': '点赞成功'})


9. 新建博客
# 做了修改,继承HookSerializer
class BlogSerializer(HookSerializer, serializers.ModelSerializer):
ctime = serializers.DateTimeField(format='%Y-%m-%d', read_only=True)
class Meta:
model = models.Blog
fields = ['id', 'category', 'image', 'title', 'summary', 'text', 'ctime', 'creator', 'comment_count', 'favor_count']
extra_kwargs = {
'creator': {'read_only': True},
'text': {'write_only': True},
}
def get_category(self, obj):
return obj.get_category_display()
def get_creator(self, obj):
return {'id': obj.creator.id, 'username': obj.creator.username}
class BlogView(APIView):
authentication_classes = [M1Authentication, M2Authentication]
def get(self, request, *args, **kwargs):
"""博客列表"""
...
def post(self, request, *args, **kwargs):
"""发表博客"""
# 判断是否登录
if not request.user:
return Response({'code': 1001, 'error': '请先登录'})
ser = BlogSerializer(data=request.data)
if not ser.is_valid():
return Response({'code': 1001, 'error': '校验失败', 'detail': ser.errors})
print(ser.validated_data)
ser.save(creator=request.user)
return Response({'code': 1000, 'data': ser.data})


浙公网安备 33010602011771号