实操学习——文章和评论的设计
- 1.文章表的设计
- 2.文章表接口演示
- 基础权限
- 创建文章
- 修改文章
- 删除文章
- 浏览所有文章
- 3.评论表的设计
- 4.评论表接口演示
- 1. 查询指定文章下的所有评论
1.文章表的设计
- 创建一个community的app
- 在settings中 完成注册
- 定义模型
创建文章表
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
- 创建序列化器
from rest_framework.serializers import ModelSerializer
#有对应的模型,可以继承ModelSerializer
class ArticleSerializer(ModelSerializer):
class Meta:
model = Article
exclude = ['is_delete']
- 创建视图
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
- 创建路由
from rest_framework.routers import DefaultRouter
from .views import *
urlpatterns = []
router = DefaultRouter()
router.register('article', ArticleViewSet)
urlpatterns += router.urls
- 创建主路由
2.文章表接口演示
基础操作分析:
创建文章:登录。自动获取当前登录的用户,作为文章的作者
修改文章:创建者
删除文章:创建者
浏览所有文章:登录
查看指定文章:登录
基础权限
由上面分析得知,最基础的权限是登录,所以我们在视图中增加基础权限的配置
创建文章
分析:获取当前登录用户为文章的创建者
- 重写create方法
#@auto_user自定义装饰器将当前登录的用户作为创建者,在前端就不需要传入user,后端会自动拿到当前登录的user作为文章的创建者
@auto_user
def create(self,request,*args,**kwargs):
return ModelViewSet.create(self,request,*args,**kwargs)
- 序列化器的优化,对返回的数据进行优化
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']
- 补充: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
修改文章
分析:文章的发布者才能修改文章。这里需要在定义一个装饰器判断该用户是否是该文章的发布者。
使用装饰器的场景:在原有功能的基础上,再增加额外的功能。
- 在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
- 重写update方法
@update_auto_user
def update(self, request, *args, **kwargs):
return ModelViewSet.update(self, request, *args, **kwargs)
删除文章
分析:只有作者才能删除文章
- 在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
- 重写destory方法
@destory_auto_user
def destory(self, request, *args, **kwargs):
return ModelViewSet.update(self, request, *args, **kwargs)
浏览所有文章
分析:针对不同的操作,查询集不同,比如,用户查看文章不能查看到草稿的数据,作者查看文章可以查看到自己的已发布的和未发布的数据
- 配置分页器
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
- 根据不同的操作设置不同的查询集。重写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)
- 查看作者自己的所有文章数据:包括已发布的和未发布的
@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方法
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文件下创建评论模型
# 评论表
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
- 定义序列化器
class CommentSerializer(ModelSerializer):
class Meta:
model = Comment
exclude = ['is_delete']
- 编写视图
分析:评论不需要更新和查询所有,所以继承GenericViewSet
class CommentViewSet(GenericViewSet,CreateModelMixin,DestroyModelMixin):
queryset = Comment.objects.filter(is_delete=False)
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)
- 增加路由
from rest_framework.routers import DefaultRouter
from .views import *
urlpatterns = []
router = DefaultRouter()
router.register('article', ArticleViewSet)
router.register('comment', CommentViewSet)
urlpatterns += router.urls
- 在文章表中增加__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
返回结果如下:
前端效果: