DRF实操学习——文章和评论的设计
- 1.文章表的设计
- 2.文章表接口演示
- 基础权限
- 创建文章
- 修改文章
- 删除文章
- 浏览所有文章
- 3.评论表的设计
- 4.评论表接口演示
- 1. 查询指定文章下的所有评论
1.文章表的设计
- 创建一个community的app
- 在settings中 完成注册
- 定义模型
创建文章表
python">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 ModelSetMixinclass 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
- 创建序列化器
python">from rest_framework.serializers import ModelSerializer#有对应的模型,可以继承ModelSerializer
class ArticleSerializer(ModelSerializer): class Meta:model = Articleexclude = ['is_delete']
- 创建视图
python">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
- 创建路由
python">from rest_framework.routers import DefaultRouterfrom .views import *urlpatterns = []
router = DefaultRouter()
router.register('article', ArticleViewSet)
urlpatterns += router.urls
- 创建主路由
2.文章表接口演示
基础操作分析:
创建文章:登录。自动获取当前登录的用户,作为文章的作者
修改文章:创建者
删除文章:创建者
浏览所有文章:登录
查看指定文章:登录
基础权限
由上面分析得知,最基础的权限是登录,所以我们在视图中增加基础权限的配置
创建文章
分析:获取当前登录用户为文章的创建者
- 重写create方法
python"> #@auto_user自定义装饰器将当前登录的用户作为创建者,在前端就不需要传入user,后端会自动拿到当前登录的user作为文章的创建者@auto_user def create(self,request,*args,**kwargs):return ModelViewSet.create(self,request,*args,**kwargs)
- 序列化器的优化,对返回的数据进行优化
python">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 = Articleexclude = ['is_delete']
- 补充:permission.py中的权限代码如下,后续修改文章、查询文章等涉及到权限的操作都会使用到本文件
python">
from functools import update_wrapperfrom django.contrib.auth.models import Group
from rest_framework.permissions import BasePermissionclass 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_groupsclass ActiveUserPermission(BasePermission):def has_permission(self, request, view):# 操作的用户必须是当前登陆的用户user = request.userreturn user.id == int(view.kwargs['pk'])class ActiveUserPPermission(BasePermission):def has_permission(self, request, view):# 操作的用户必须是当前登陆的用户user = request.userreturn user.id == int(request.data.get('user'))class RootPermission(BasePermission):def has_permission(self, request, view):user = request.userreturn user.is_superuser# 更改权限装饰器
def wrap_permisssion(*permissions, validate_permisssion=True):def decorator(func):def wrapper(self, request, *args, **kwargs):self.permission_classes = permissionsif validate_permisssion:self.check_permissions(request)return func(self, request, *args, **kwargs)return update_wrapper(wrapper, func)return decoratordef auto_user(func):def wrapper(self, request, *args, **kwargs):request.POST._mutable = True # 让请求参数可以被修改request.data['user'] = request.user.idreturn func(self, request, *args, **kwargs)return wrapper
修改文章
分析:文章的发布者才能修改文章。这里需要在定义一个装饰器判断该用户是否是该文章的发布者。
使用装饰器的场景:在原有功能的基础上,再增加额外的功能。
- 在permission.py中增加装饰器update_auto_user
python">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.idreturn func(self, request, *args, **kwargs)return wrapper
- 重写update方法
python"> @update_auto_userdef update(self, request, *args, **kwargs):return ModelViewSet.update(self, request, *args, **kwargs)
删除文章
分析:只有作者才能删除文章
- 在permission.py中增加装饰器destory_auto_user
python">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
- 重写destory方法
python"> @destory_auto_userdef destory(self, request, *args, **kwargs):return ModelViewSet.update(self, request, *args, **kwargs)
浏览所有文章
分析:针对不同的操作,查询集不同,比如,用户查看文章不能查看到草稿的数据,作者查看文章可以查看到自己的已发布的和未发布的数据
- 配置分页器
python">from rest_framework.pagination import PageNumberPagination
class ArticlePaginationPageNumber(PageNumberPagination):page_size = 10 # 默认每页多少条,如果不传size,默认3条#page_size_param = 'page' #定义传入页数的参数,默认为pagepage_size_query_param = 'size' # 规定哪个参数为分页大小参数,参数可以自己定义,这里定义为size,则前端传入参数size = 10,每页展示10条,但是不会超过设置的最大每页条数100max_page_size = 100 # 最大每页多少条class ArticleViewSet(ModelViewSet):queryset = Article.objects.filter(is_delete=False)serializer_class = ArticleSerializerpermission_classes = [IsAuthenticated]#在视图中增加分页器类pagination_class = ArticlePaginationPageNumber
- 根据不同的操作设置不同的查询集。重写get_queryset方法
python">class ArticleViewSet(ModelViewSet):queryset = Article.objects.filter(is_delete=False)serializer_class = ArticleSerializerpermission_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_userdef update(self, request, *args, **kwargs):return ModelViewSet.update(self, request, *args, **kwargs)@destory_auto_userdef destory(self, request, *args, **kwargs):return ModelViewSet.update(self, request, *args, **kwargs)
- 查看作者自己的所有文章数据:包括已发布的和未发布的
python"> @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。
重写retrieve方法
python"> 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.评论表的设计
- 在community的models文件下创建评论模型
python"># 评论表
class Comment(ModelSetMixin):content = models.TextField(verbose_name='评论的内容')level = models.IntegerField(verbose_name='评论等级',default=1)# 因为在文章下的评论没有父级评论,所以null=True允许为空# 父级评论的idparent_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
- 定义序列化器
python">class CommentSerializer(ModelSerializer): class Meta:model = Commentexclude = ['is_delete']
- 编写视图
分析:评论不需要更新和查询所有,所以继承GenericViewSet
python">class CommentViewSet(GenericViewSet,CreateModelMixin,DestroyModelMixin):queryset = Comment.objects.filter(is_delete=False,article__status=1)serializer_class = CommentSerializerpermission_classes = [IsAuthenticated]@auto_user def create(self,request,*args,**kwargs):return ModelViewSet.create(self,request,*args,**kwargs)@destory_auto_userdef destory(self, request, *args, **kwargs):return ModelViewSet.update(self, request, *args, **kwargs)
- 增加路由
python">from rest_framework.routers import DefaultRouterfrom .views import *urlpatterns = []
router = DefaultRouter()
router.register('article', ArticleViewSet)
router.register('comment', CommentViewSet)
urlpatterns += router.urls
- 在文章表中增加__str__方法
这样在评论表中的article外键就会展示出对应的内容
4.评论表接口演示
1. 查询指定文章下的所有评论
分析:
/article/1/comment
给文章视图集编写一个额外的功能,返回该文章的所有评论
在ArticleViewSet中增加接口如下:
python"> @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)
优化返回结果,修改序列化器:
python"># 定义子评论的序列化器
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 = Commentexclude = ['is_delete']def get_reply_username(self,comment):# 查询出被回复评论的用户的用户名return Comment.objects.get(id=comment.reply_comment).user.usernameclass 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 = Commentexclude = ['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
返回结果如下:
前端效果: