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)
  1. 定义一个路由,用来创建数据库信息
    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')
  1. 展示所有用户信息
    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)
  1. 博客详细信息展示
    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)

image

4.展示博客对应的所有评论
path('api/comment/<int:blog_id>', views.CommentView.as_view())

这里编写序列化类时继承了父类HookSerializer,基于源码改写Serializerto_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)

结果:
image

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})

密码不一致或者没传某个字段信息都会抛出错误
image
校验成功
image
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.userrequest.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})

image
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': '点赞成功'})

image
image
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})

image

posted @ 2025-09-29 14:17  暴力丸  阅读(2)  评论(0)    收藏  举报