DRF实操学习——文章和评论的设计

1.文章表的设计

  1. 创建一个community的app
    在这里插入图片描述
  2. 在settings中 完成注册
    在这里插入图片描述
  3. 定义模型
    创建文章表
from django.db import models
from work.models import Label
from django.contrib.auth.models import User
# Create your models here.
from utils.modelsMixin import ModelSetMixin

class Article(ModelSetMixin):
    STATUS_CHOICES = (
        (0,'未发布'),
        (1,'发布'),
    )
    title = models.CharField(max_length=100,verbose_name='标题')
    digest = models.CharField(max_length=300,verbose_name='摘要')
    content = models.TextField(verbose_name='文章内容')
    page_view = models.IntegerField(verbose_name='浏览量',default=0)
    priority = models.IntegerField(verbose_name='优先级',default=0)
    status = models.IntegerField(verbose_name='状态',default=0,choices=STATUS_CHOICES)

    label = models.ForeignKey(Label,on_delete=models.CASCADE)
    user = models.ForeignKey(User,on_delete=models.CASCADE)

    class Meta:
        # 按照priority、page_view、create_time进行倒叙排序
        ordering = ["-priority","-page_view","-create_time"]
        db_table = 'article' #定义生成的mysql表名
        verbose_name = '文章'
        verbose_name_plural = verbose_name

  1. 创建序列化器
from rest_framework.serializers import ModelSerializer


#有对应的模型,可以继承ModelSerializer
class ArticleSerializer(ModelSerializer): 

    class Meta:
        model = Article
        exclude = ['is_delete']

  1. 创建视图
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from .serializers import *
# Create your views here.
class ArticleViewSet(ModelViewSet):
    queryset = Article.objects.filter(is_delete=False)
    serializer_class = ArticleSerializer
  1. 创建路由
from rest_framework.routers import DefaultRouter

from .views import *

urlpatterns = []
router = DefaultRouter()
router.register('article', ArticleViewSet)
urlpatterns += router.urls

  1. 创建主路由
    在这里插入图片描述

2.文章表接口演示

基础操作分析:
创建文章:登录。自动获取当前登录的用户,作为文章的作者
修改文章:创建者
删除文章:创建者
浏览所有文章:登录
查看指定文章:登录

基础权限

由上面分析得知,最基础的权限是登录,所以我们在视图中增加基础权限的配置
在这里插入图片描述

创建文章

分析:获取当前登录用户为文章的创建者

  1. 重写create方法
    #@auto_user自定义装饰器将当前登录的用户作为创建者,在前端就不需要传入user,后端会自动拿到当前登录的user作为文章的创建者
    @auto_user 
    def create(self,request,*args,**kwargs):
        return ModelViewSet.create(self,request,*args,**kwargs)
  1. 序列化器的优化,对返回的数据进行优化
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from .models import *

#有对应的模型,可以继承ModelSerializer
class ArticleSerializer(ModelSerializer): 
    user_name = serializer.CharField(source='user.username',read_only=True)
    user_avatar = serializer.CharField(source='user.userdetail.avatar',read_only=True)
    label_name = serializer.CharField(source='label.name',read_only=True)
    class Meta:
        model = Article
        exclude = ['is_delete']

  1. 补充:permission.py中的权限代码如下,后续修改文章、查询文章等涉及到权限的操作都会使用到本文件

from functools import update_wrapper

from django.contrib.auth.models import Group
from rest_framework.permissions import BasePermission


class TeacherPermission(BasePermission):
    def has_permission(self, request, view):
        user = request.user  # 当前请求的用户信息
        # 判断身份,查询用户在不在老师这个分组里面
        group = Group.objects.filter(name='老师').first()
        user_groups = user.groups.all()
        return user.is_superuser or group in user_groups


class ActiveUserPermission(BasePermission):
    def has_permission(self, request, view):
        # 操作的用户必须是当前登陆的用户
        user = request.user
        return user.id == int(view.kwargs['pk'])

class ActiveUserPPermission(BasePermission):
    def has_permission(self, request, view):
        # 操作的用户必须是当前登陆的用户
        user = request.user
        return user.id == int(request.data.get('user'))


class RootPermission(BasePermission):
    def has_permission(self, request, view):
        user = request.user
        return user.is_superuser


# 更改权限装饰器
def wrap_permisssion(*permissions, validate_permisssion=True):
    def decorator(func):
        def wrapper(self, request, *args, **kwargs):
            self.permission_classes = permissions
            if validate_permisssion:
                self.check_permissions(request)
            return func(self, request, *args, **kwargs)

        return update_wrapper(wrapper, func)

    return decorator


def auto_user(func):
    def wrapper(self, request, *args, **kwargs):
        request.POST._mutable = True  # 让请求参数可以被修改
        request.data['user'] = request.user.id

        return func(self, request, *args, **kwargs)

    return wrapper

修改文章

分析:文章的发布者才能修改文章。这里需要在定义一个装饰器判断该用户是否是该文章的发布者。
使用装饰器的场景:在原有功能的基础上,再增加额外的功能。

  1. 在permission.py中增加装饰器update_auto_user
def update_auto_user(func):
    def wrapper(self, request, *args, **kwargs):
        # 判断当前操作的用户是不是这个数据的创建者
        # 设:文章的id为1,作者的id为3,当前登录的用户id为5
        # 所有的没有被逻辑删除的文章数据集里面的id等于1,并且user用户为5,查不到作者id为3的数据
        # self.get_queryset()可以应用于所有的视图集
        res = self.get_queryset().filter(id=kwargs['pk'],user=request.user)
        if not res:
            return Response({'detail':'您没有修改的权限'})
        # 修改不可以更改作者,只能是当前的登录用户
        request.POST._mutable = True  # 让请求参数可以被修改
        request.data['user'] = request.user.id
        return func(self, request, *args, **kwargs)

    return wrapper
  1. 重写update方法
    @update_auto_user
    def update(self, request, *args, **kwargs):
        return ModelViewSet.update(self, request, *args, **kwargs)

删除文章

分析:只有作者才能删除文章

  1. 在permission.py中增加装饰器destory_auto_user
def destory_auto_user(func):
    def wrapper(self, request, *args, **kwargs):
        res = self.get_queryset().filter(id=kwargs['pk'],user=request.user)
        if not res:
            return Response({'detail':'您没有删除的权限'})
        return func(self, request, *args, **kwargs)

    return wrapper
  1. 重写destory方法
    @destory_auto_user
    def destory(self, request, *args, **kwargs):
        return ModelViewSet.update(self, request, *args, **kwargs)

浏览所有文章

分析:针对不同的操作,查询集不同,比如,用户查看文章不能查看到草稿的数据,作者查看文章可以查看到自己的已发布的和未发布的数据

  1. 配置分页器
from rest_framework.pagination import PageNumberPagination
class ArticlePaginationPageNumber(PageNumberPagination):
    page_size = 10  # 默认每页多少条,如果不传size,默认3条
    #page_size_param  = 'page' #定义传入页数的参数,默认为page
    page_size_query_param = 'size'  # 规定哪个参数为分页大小参数,参数可以自己定义,这里定义为size,则前端传入参数size = 10,每页展示10条,但是不会超过设置的最大每页条数100
    max_page_size = 100  # 最大每页多少条


class ArticleViewSet(ModelViewSet):
    queryset = Article.objects.filter(is_delete=False)
    serializer_class = ArticleSerializer
    permission_classes = [IsAuthenticated]
    #在视图中增加分页器类
    pagination_class = ArticlePaginationPageNumber
  1. 根据不同的操作设置不同的查询集。重写get_queryset方法
class ArticleViewSet(ModelViewSet):
    queryset = Article.objects.filter(is_delete=False)
    serializer_class = ArticleSerializer
    permission_classes = [IsAuthenticated]
    pagination_class = ArticlePaginationPageNumber

    # 重写get_queryset方法的场景:根据不同的操作场景设置不同的查询集
    def get_queryset(self):
        if self.action in ['list','retrieve']: #如果是查询,则只能返回已发布的文章数据
            return Article.objects.filter(is_delete=False,status=1)
        return self.queryset

    #@auto_user自定义装饰器将当前登录的用户作为创建者,在前端就不需要传入user,后端会自动拿到当前登录的user作为文章的创建者
    @auto_user 
    def create(self,request,*args,**kwargs):
        return ModelViewSet.create(self,request,*args,**kwargs)

    @update_auto_user
    def update(self, request, *args, **kwargs):
        return ModelViewSet.update(self, request, *args, **kwargs)

    @destory_auto_user
    def destory(self, request, *args, **kwargs):
        return ModelViewSet.update(self, request, *args, **kwargs)
  1. 查看作者自己的所有文章数据:包括已发布的和未发布的
    @action(methods=['get'],detail=False)
    def my(self,request):
        """
        得到用户自己的数据,包括未发布数据
        """
        # 用户为当前登录的用户
        data = self.get_queryset().filter(user=request.user) 
        serializer = self.get_serializer(data,many=True)
        return Response(serializer.data)
  1. 浏览量增加接口
    分析:点击进入文章,浏览量+1。
    重写retrieve方法
    def retrieve(self, request, *args, **kwargs):
        """
        重写retrieve方法,点击文章,增加浏览量
        """
        # F查询可以拿到这个字段的值
        self.get_queryset().filter(id=kwargs['pk']).update(page_view=F('page_view')+1)
        return ModelViewSet.update(self, request, *args, **kwargs)

补充:如果不想被刷浏览量,前端写延时器,10s之后调用接口。增加额外的增加浏览量的接口,retrieve可以记录客户端传入的id,然后判断时间。

3.评论表的设计

  1. 在community的models文件下创建评论模型
# 评论表
class Comment(ModelSetMixin):
    content = models.TextField(verbose_name='评论的内容')
    level = models.IntegerField(verbose_name='评论等级',default=1)
    # 因为在文章下的评论没有父级评论,所以null=True允许为空
    # 父级评论的id
    parent_comment = models.IntegerField(verbose_name='父级评论',null=True)
    # 回复评论的id,因为有可能是回复父级评论下面的子评论,所以要增加这个字段
    reply_comment = models.IntegerField(verbose_name='回复评论',null=True)

    # 一篇文章可以有多条评论
    article = models.ForeignKey(Article,on_delete=models.CASCADE)
    # 一个用户可以评论多条评论
    user = models.ForeignKey(User,on_delete=models.CASCADE)

    class Meta:
        # 按照create_time进行倒叙排序
        ordering = ["-create_time"]
        db_table = 'comment' #定义生成的mysql表名
        verbose_name = '评论'
        verbose_name_plural = verbose_name
  1. 定义序列化器
class CommentSerializer(ModelSerializer): 
    class Meta:
        model = Comment
        exclude = ['is_delete']
  1. 编写视图
    分析:评论不需要更新和查询所有,所以继承GenericViewSet
class CommentViewSet(GenericViewSet,CreateModelMixin,DestroyModelMixin):
    queryset = Comment.objects.filter(is_delete=False,article__status=1)
    serializer_class = CommentSerializer
    permission_classes = [IsAuthenticated]

    @auto_user 
    def create(self,request,*args,**kwargs):
        return ModelViewSet.create(self,request,*args,**kwargs)

    @destory_auto_user
    def destory(self, request, *args, **kwargs):
        return ModelViewSet.update(self, request, *args, **kwargs)
  1. 增加路由
from rest_framework.routers import DefaultRouter

from .views import *

urlpatterns = []
router = DefaultRouter()
router.register('article', ArticleViewSet)
router.register('comment', CommentViewSet)
urlpatterns += router.urls

  1. 在文章表中增加__str__方法
    在这里插入图片描述
    这样在评论表中的article外键就会展示出对应的内容
    在这里插入图片描述

4.评论表接口演示

1. 查询指定文章下的所有评论

分析:
/article/1/comment
给文章视图集编写一个额外的功能,返回该文章的所有评论
在ArticleViewSet中增加接口如下:

    @action(methods=['get'],detail=True)
    def comment(self,request,pk):
        # article_id=pk联表查询
        # 查看所有的评论中文章id为传入的pk,等级为1的评论
        comments = Comment.objects.filter(article_id=pk,level=1)
        serializer = CommentSerializer(comments,many=True)
        return Response(serializer.data)

优化返回结果,修改序列化器:

# 定义子评论的序列化器
class SonCommentSerializer(ModelSerializer):
    user_name = serializer.CharField(source='user.username',read_only=True)
    user_avatar = serializer.CharField(source='user.userdetail.avatar',read_only=True)
    reply_username = SerializerMethodField()

        model = Comment
        exclude = ['is_delete']

    def get_reply_username(self,comment):
        # 查询出被回复评论的用户的用户名
        return Comment.objects.get(id=comment.reply_comment).user.username

class CommentSerializer(ModelSerializer): 
    user_name = serializer.CharField(source='user.username',read_only=True)
    user_avatar = serializer.CharField(source='user.userdetail.avatar',read_only=True)
    # 返回当前评论的子评论
    sonComment = SerializerMethodField()

    class Meta:
        model = Comment
        exclude = ['is_delete']

    # get_+自定义的roleDetail名字  comment保存当前要操作的模型对象
    def get_sonComment(self, comment):
        serializer = SonCommentSerializer(Comment.objects.filter(parent_comment=comment.id),many=True)
        data = serializer.data
        # 将获取的子评论进行反转,第一个评论在第一位,而不是时间最新的在最前面
        # reverse对列表做反转
        data.reverse()
        return data


返回结果如下:
在这里插入图片描述
前端效果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值