1.6--drf实战案例
1. 需求
请结合上述学习的drf知识开发 简易版《抽屉新热榜》。其中包含的功能如下:
-
注册
输入:手机号、用户名、密码、确认密码。
-
登录
输入:手机号 或 用户名 + 密码
注意:登录成功后给用户返回token,后续请求需要在url中携带token(有效期2周) -
我的话题
- 我的话题列表
- 创建话题
- 修改话题
- 删除话题(逻辑删除) -
我的资讯
- 创建资讯(5分钟创建一个,需要根据用户限流) 问题1:5/h 2/m; 问题2:成功后,下次再创建;
- 文本(你问我答、42区、挨踢1024、段子)
- 图片(图片、你问我答、42区、挨踢1024、段子)
- 连接(图片、你问我答、42区、挨踢1024、段子)
注意:创建时默认自己做1个推荐。
- 我的资讯列表 -
首页
- 资讯首页
- 时间倒序,读取已审核通过的资讯
- 加载更多,分页处理
- 支持传入参数,查询各分区资讯:图片、你问我答、42区、挨踢1024、段子 ?zone=2 -
推荐
- 推荐
- 取消推荐
- 我的推荐列表 -
收藏
- 收藏 or 取消收藏
- 我的收藏列表 -
评论
- 查看评论列表
- 根据【后代的更新时间】从大到小排序,读取根评论,每次读20条。
- 读取根评论先关的子评论。
- 将子评论挂靠到跟评论上,最终形成父子关系通过JSON返回给前端。
注意:自己也可以通过depth实现逐步读取子评论(此处不这样操作)
- 创建评论
- 判断是根评论 or 回复
- 回复时,深度+1
- 评论后,找到根评论去更新【后代的更新时间】 -
2. 参考表结构
表结构参考:

from django.db import models class DeletedModel(models.Model): deleted = models.BooleanField(verbose_name="已删除", default=False) class Meta: abstract = True class UserInfo(DeletedModel): """ 用户表 """ username = models.CharField(verbose_name="用户名", max_length=32) phone = 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) token_expiry_date = models.DateTimeField(verbose_name="token有效期", null=True, blank=True) status_choice = ( (1, "激活"), (2, "禁用"), ) status = models.IntegerField(verbose_name="状态", choices=status_choice, default=1) create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) class Meta: # The newer indexes option provides more functionality than index_together # index_together may be deprecated in the future. # https://docs.djangoproject.com/en/3.2/ref/models/options/#index-together indexes = [ models.Index(fields=['username', "password"], name='idx_name_pwd') ] class Topic(DeletedModel): """ 话题 """ title = models.CharField(verbose_name="话题", max_length=16, db_index=True) is_hot = models.BooleanField(verbose_name="热门话题", default=False) user = models.ForeignKey(verbose_name="用户", to="UserInfo", on_delete=models.CASCADE) create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) class News(DeletedModel): """ 新闻资讯 """ zone_choices = ((1, "42区"), (2, "段子"), (3, "图片"), (4, "挨踢1024"), (5, "你问我答")) zone = models.IntegerField(verbose_name="专区", choices=zone_choices) title = models.CharField(verbose_name="文字", max_length=150) url = models.CharField(verbose_name="链接", max_length=200, null=True, blank=True) # xxxxx?xxxxxx.png,xxxxxxxx.jeg image = models.TextField(verbose_name="图片地址", help_text="逗号分割", null=True, blank=True) topic = models.ForeignKey(verbose_name="话题", to="Topic", on_delete=models.CASCADE, null=True, blank=True) user = models.ForeignKey(verbose_name="用户", to="UserInfo", on_delete=models.CASCADE) create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) status_choice = ( (1, "待审核"), (2, "已通过"), (3, "未通过"), ) status = models.IntegerField(verbose_name="状态", choices=status_choice, default=1) collect_count = models.IntegerField(verbose_name="收藏数", default=0) recommend_count = models.IntegerField(verbose_name="推荐数", default=0) comment_count = models.IntegerField(verbose_name="评论数", default=0) class Collect(models.Model): """ 收藏 """ news = models.ForeignKey(verbose_name="资讯", to="News", 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: # unique_together = [['news', 'user']] constraints = [ models.UniqueConstraint(fields=['news', 'user'], name='uni_collect_news_user') ] class Recommend(models.Model): """ 推荐 """ news = models.ForeignKey(verbose_name="资讯", to="News", 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=['news', 'user'], name='uni_recommend_news_user') ] class Comment(models.Model): """ 评论表 """ news = models.ForeignKey(verbose_name="资讯", to="News", on_delete=models.CASCADE) user = models.ForeignKey(verbose_name="用户", to="UserInfo", on_delete=models.CASCADE) content = models.CharField(verbose_name="内容", max_length=150) depth = models.IntegerField(verbose_name="深度", default=0) root = models.ForeignKey(verbose_name="根评论", to="Comment", related_name="descendant", on_delete=models.CASCADE, null=True, blank=True) reply = models.ForeignKey(verbose_name="回复", to="Comment", related_name="reply_list", on_delete=models.CASCADE, null=True, blank=True) create_datetime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) # 针对根评论 descendant_update_datetime = models.DateTimeField(verbose_name="后代更新时间", auto_now_add=True)
3.1 环境准备
-
基于django创建项目,例如:dig
-
安装必备模块
pip install ****
django-filter==2.4.0 django-redis==5.0.0 djangorestframework==3.12.4
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'django_filters', 'api.apps.ApiConfig' ]
-
python manage.py makemigrations
- python manage.py migrate
3.2 接口开发
详细见代码
1、注册登录
dig\urls

from django.contrib import admin from django.urls import path, include urlpatterns = [ path('api/', include('api.urls')), ]
api\urls

from django.urls import path from rest_framework import routers from api.views import account # # 以前写路由 # urlpatterns = [ # # /api/register/ 用户访问注册 执行RegisterView视图 注册需要输入用户名密码 用post形式提交 # path('register/', account.RegisterView.as_view({"post": "create"})), # ] # # drf中用SimpleRouter # router = routers.SimpleRouter() # # /api/register/ 用户访问注册 # # basename='x'也可以为None不写 # # x-list 生成的列表页面 根据这个name可以反向生成url 源码里有体现 # # x-create 生成的添加页面 # # (运行程序可能会出错 )需要在视图里RegisterView 定义queryset # router.register(r'register', account.RegisterView,'x') # urlpatterns = [ # # 扩展url # ] # urlpatterns += router.urls # 通过以上就有了下面 router = routers.SimpleRouter() # 注册/api/register/ 访问注册视图RegisterView 需要post方法 router.register(r'register', account.RegisterSerializer, 'register')
views\account

import uuid import datetime from django.db.models import Q from django.urls import reverse from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet from rest_framework.response import Response from api.extension import return_code from api.extension.mixins import DigCreateModelMixin from api import models from rest_framework.mixins import CreateModelMixin from api.serializers.account import RegisterSerializer, AuthSerializer """ 1. 只需要提供POST方法 2. 请求进来执行 DigCreateModelMixin的create方法 3. 获取数据request.data,进行校验(RegisterSerializer) """ # DigCreateModelMixin自定义的 (post对应的是create方法) class RegisterView(DigCreateModelMixin, GenericViewSet): """ 用户注册 """ authentication_classes = [] permission_classes = [] serializer_class = RegisterSerializer # 验证通过 创建用户 def perform_create(self, serializer): #confirm_password 此字段数据库里没有 用完需要剔除掉pop serializer.validated_data.pop('confirm_password') # super().perform_create(serializer) serializer.save() class AuthView(APIView): """ 用户登录 """ authentication_classes = [] permission_classes = [] # 2. 数据库校验用户名和密码的合法性 def post(self, request): # 1. 获取用户请求 & 校验 serializer = AuthSerializer(data=request.data) if not serializer.is_valid(): # { 'username':[错误信息,], 'phone':[xxxx,]} return Response({"code": return_code.VALIDATE_ERROR, 'detail': serializer.errors}) username = serializer.validated_data.get('username') phone = serializer.validated_data.get('phone') password = serializer.validated_data.get('password') # Q查询 用户名和手机号是或的关系 和密码是且的关系 user_object = models.UserInfo.objects.filter(Q(Q(username=username) | Q(phone=phone)), password=password).first() if not user_object: return Response({"code": return_code.VALIDATE_ERROR, "error": "用户名或密码错误"}) # 如果查询到了生成一个token token = str(uuid.uuid4()) user_object.token = token # 设置token有效期:当前时间 + 2周 user_object.token_expiry_date = datetime.datetime.now() + datetime.timedelta(weeks=2) user_object.save() return Response({"code": return_code.SUCCESS, "data": {"token": token, "name": user_object.username}})
extension\mixins(重写了CreateModelMixin)

from rest_framework import mixins from rest_framework.response import Response from api.extension import return_code # 重写了CreateModelMixin class DigCreateModelMixin(mixins.CreateModelMixin): def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) # 1. 异常处理(CreateModelMixin里的异常出里不是我们想要的) if not serializer.is_valid(): return Response({"code": return_code.VALIDATE_ERROR, 'detail': serializer.errors}) # 2. 优化perform_create res = self.perform_create(serializer) # 3. 返回数据的处理(将if判断语句简化处理)如果res有值也就是true执行res 否则执行后面 return res or Response({"code": return_code.SUCCESS, 'data': serializer.data})
serializers\account

from rest_framework import serializers from rest_framework.exceptions import ValidationError from api import models class RegisterSerializer(serializers.ModelSerializer): # 库里没有这个字段 rite_only=True 需要数据校验 不需要序列化的用完还得需要剔除掉pop confirm_password = serializers.CharField(label="确认密码", min_length=8, write_only=True) password = serializers.CharField(label="密码", min_length=8, write_only=True) #'username', "phone", 这俩字段需要校验也需要序列化返回 class Meta: model = models.UserInfo fields = ['username', "phone", "password", "confirm_password"] # 钩子函数 用户名和手机号 去数据库校验 def validate_username(self, value): # deleted=False所有未删除的用户对比是否存在 exists = models.UserInfo.objects.filter(username=value, deleted=False).exists() if exists: raise ValidationError("用户名已存在") return value def validate_phone(self, value): exists = models.UserInfo.objects.filter(phone=value, deleted=False).exists() if exists: raise ValidationError("手机号已存在") return value # 密码的校验 def validate_confirm_password(self, value): password = self.initial_data.get('password') if password == value: return value raise ValidationError("两次密码不一致") class AuthSerializer(serializers.Serializer): username = serializers.CharField(label="用户名", write_only=True, required=False) # required=False 客户端可以不提交 phone = serializers.CharField(label="手机", write_only=True, required=False) # 不提交 password = serializers.CharField(label="密码", min_length=8, write_only=True) # 钩子函数 def validate_username(self, value): # 获取用户名和手机号 username = self.initial_data.get("username") phone = self.initial_data.get("phone") # 用户名和手机号都不存在 if not username and not phone: raise ValidationError("用户名或手机为空") # 用户名和手机号一起提交 也不行 只能提交一个 if username and phone: raise ValidationError("提交数据异常") return value
extension\auth(认证类封装)

import datetime from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from api.extension import return_code from api import models # 认证类 # 用这个认证类的话 # 必须认证成功之后才能访问 class TokenAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get("token") if not token: raise AuthenticationFailed({"code": return_code.AUTH_FAILED, "error": "认证失败"}) user_object = models.UserInfo.objects.filter(token=token).first() if not user_object: raise AuthenticationFailed({"code": return_code.AUTH_FAILED, "error": "认证失败"}) if datetime.datetime.now() > user_object.token_expiry_date: raise AuthenticationFailed({"code": return_code.AUTH_OVERDUE, "error": "认证过期"}) return user_object, token def authenticate_header(self, request): return 'Bearer realm="API"' # 用这个认证类可登录也可不登陆 # 登录,可以访问 request.user # 不登录,也可以访问 request.user=None class UserAnonTokenAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get("token") if not token: return None user_object = models.UserInfo.objects.filter(token=token).first() if not user_object: return None if datetime.datetime.now() > user_object.token_expiry_date: return None return user_object, token def authenticate_header(self, request): return 'Bearer realm="API"'
extension\return_code(封装返回的常量值)

# 成功 SUCCESS = 0 # 用户提交数据校验失败 VALIDATE_ERROR = 1001 # 认证失败 AUTH_FAILED = 2000 # 认证过期 AUTH_OVERDUE = 2001 # 无权访问 PERMISSION_DENIED = 3000 # 无权访问 TOO_MANY_REQUESTS = 4000
settings(时区的修改 app的注册)

# 在登录token那设置时间 datetime.datetime.now() / datetime.datetime.utcnow() => utc时间 # TIME_ZONE = 'UTC' # datetime.datetime.now() - 东八区时间 / datetime.datetime.utcnow() => utc时间 TIME_ZONE = 'Asia/Shanghai' # 影响自动生成数据库时间字段; # USE_TZ = True,创建UTC时间写入到数据库。 # USE_TZ = False,根据TIME_ZONE设置的时区进行创建时间并写入数据库 USE_TZ = False
2、话题
api/urls(注意routers.SimpleRouter()的使用)

from django.urls import path, include from rest_framework import routers from api.views import account ,topic # # 以前写路由 # urlpatterns = [ # # /api/register/ 用户访问注册 执行RegisterView视图 注册需要输入用户名密码 用post形式提交 # path('register/', account.RegisterView.as_view({"post": "create"})), # ] # # # drf中用SimpleRouter # router = routers.SimpleRouter() # # /api/register/ 用户访问注册 # # basename='x'也可以为None不写 # # x-list 生成的列表页面 根据这个name可以反向生成url 源码里有体现 # # x-create 生成的添加页面 # # (运行程序可能会出错 )需要在视图里RegisterView 定义queryset # router.register(r'register', account.RegisterView,'x') # urlpatterns = [ # # 扩展url # ] # urlpatterns += router.urls # 通过以上就有了下面 router = routers.SimpleRouter() # 注册/api/register/ 访问注册视图RegisterView 需要post方法 router.register(r'register', account.RegisterView, 'register') # print(router.register(r'register', account.RegisterView, 'register')) print(router.urls) # 创建话题(认证成功才能访问 当前用户是谁) router.register(r'topic', topic.TopicView) # # # 我的资讯 # router.register(r'news', news.NewsView) # # # 资讯首页 # router.register(r'index', news.IndexView) # # # 收藏 # router.register(r'collect', collect.CollectView) # # # 推荐 # router.register(r'recommend', recommend.RecommendView) # # # 评论 # router.register(r'comment', comment.CommentView) urlpatterns = [ # path('register/', account.RegisterView.as_view({"post": "create"})), path('auth/', account.AuthView.as_view()), ] urlpatterns += router.urls print(urlpatterns)
topic.py(view)(注意 分页的使用 查询的条件filter)

from django_filters.rest_framework import DjangoFilterBackend, filters, FilterSet from rest_framework.viewsets import GenericViewSet from api.extension.filter import SelfFilterBackend from api.extension.mixins import DigCreateModelMixin, DigDestroyModelMixin, DigUpdateModelMixin, DigListModelMixin from api import models from api.extension.page import DigLimitOffsetPagination from api.serializers.topic import TopicSerializer # 分页组件 class TopicFilterSet(FilterSet): # 分页查看更多避免数据重复记录一下最后一个的id在继续往后查 # ?latest_id=99 ----> id<99 # ?latest_id=99&limit=10 ----> id<99 and limit10 latest_id = filters.NumberFilter(field_name="id", lookup_expr="lt") class Meta: model = models.Topic fields = ["latest_id"] class TopicView(DigCreateModelMixin, DigDestroyModelMixin, DigUpdateModelMixin, DigListModelMixin, GenericViewSet): '''主题''' # 在未删除的话题基础上加上一个当前用户(SelfFilterBackend增删改都要用到 只能用自己的) # settings文件配置好分页 (DigLimitOffsetPagination) DjangoFilterBackend查询条件 filter_backends = [SelfFilterBackend, DjangoFilterBackend] filterset_class = TopicFilterSet # 认证配置在settings文件中 所有的认证成功之后才能访问 # DigListModelMixin 获取列表(集成分页) # 未删除的话题 queryset = models.Topic.objects.filter(deleted=False).order_by('-id') # 序列化类 接收用户的请求校验 对fields列表的里的字段进行校验 serializer_class = TopicSerializer # 新增 执行create方法 DigCreateModelMixin def perform_create(self, serializer): serializer.save(user=self.request.user) # 删除 DigDestroyModelMixin def perform_destroy(self, instance): # 逻辑删除 不做物理删除 instance.deleted = True instance.save()
settings.py配置分页

REST_FRAMEWORK = { # 版本配置 "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning", "DEFAULT_VERSION": "v1", "ALLOWED_VERSIONS": ["v1"], "VERSION_PARAM": "version", # # 认证配置(TokenAuthentication认证类) "DEFAULT_AUTHENTICATION_CLASSES": ["api.extension.auth.TokenAuthentication", ], "UNAUTHENTICATED_USER": lambda: None, "UNAUTHENTICATED_TOKEN": lambda: None, # # 分页配置 "DEFAULT_PAGINATION_CLASS": "api.extension.page.DigLimitOffsetPagination" }
page.py(分页类)

from rest_framework.pagination import LimitOffsetPagination class DigLimitOffsetPagination(LimitOffsetPagination): default_limit = 10 max_limit = 100 offset_query_param = None
filter.py(获取当前用户 在增删改 话题时)

from rest_framework.filters import BaseFilterBackend class SelfFilterBackend(BaseFilterBackend): def filter_queryset(self, request, queryset, view): # 获取当前用户 return queryset.filter(user=request.user)
mixin.py(topic的增删改查)

from rest_framework import mixins from rest_framework.response import Response from api.extension import return_code # 重写了CreateModelMixin class DigCreateModelMixin(mixins.CreateModelMixin): def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) # 1. 异常处理(CreateModelMixin里的异常出里不是我们想要的) if not serializer.is_valid(): return Response({"code": return_code.VALIDATE_ERROR, 'detail': serializer.errors}) # 2. 优化perform_create res = self.perform_create(serializer) # 3. 返回数据的处理(将if判断语句简化处理)如果res有值也就是true执行res 否则执行后面 return res or Response({"code": return_code.SUCCESS, 'data': serializer.data}) class DigListModelMixin(mixins.ListModelMixin): def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return Response({"code": return_code.SUCCESS, 'data': serializer.data}) serializer = self.get_serializer(queryset, many=True) return Response({"code": return_code.SUCCESS, 'data': serializer.data}) class DigDestroyModelMixin(mixins.DestroyModelMixin): def destroy(self, request, *args, **kwargs): instance = self.get_object() res = self.perform_destroy(instance) return res or Response({"code": return_code.SUCCESS}) class DigUpdateModelMixin(mixins.UpdateModelMixin): def destroy(self, request, *args, **kwargs): partial = kwargs.pop('partial', False) instance = self.get_object() serializer = self.get_serializer(instance, data=request.data, partial=partial) if not serializer.is_valid(): return Response({"code": return_code.VALIDATE_ERROR, 'detail': serializer.errors}) res = self.perform_update(serializer) return res or Response({"code": return_code.SUCCESS, 'data': serializer.data})
3.我的资讯列表
1、查看资讯列表 2、创建资讯 3、限流 (django的缓存redis)
api/urls(注意routers.SimpleRouter()的使用)

from django.urls import path, include from rest_framework import routers from api.views import account, topic, news # # 以前写路由 # urlpatterns = [ # # /api/register/ 用户访问注册 执行RegisterView视图 注册需要输入用户名密码 用post形式提交 # path('register/', account.RegisterView.as_view({"post": "create"})), # ] # # # drf中用SimpleRouter # router = routers.SimpleRouter() # # /api/register/ 用户访问注册 # # basename='x'也可以为None不写 # # x-list 生成的列表页面 根据这个name可以反向生成url 源码里有体现 # # x-create 生成的添加页面 # # (运行程序可能会出错 )需要在视图里RegisterView 定义queryset # router.register(r'register', account.RegisterView,'x') # urlpatterns = [ # # 扩展url # ] # urlpatterns += router.urls # 通过以上就有了下面 router = routers.SimpleRouter() # 注册/api/register/ 访问注册视图RegisterView 需要post方法 router.register(r'register', account.RegisterView, 'register') # print(router.register(r'register', account.RegisterView, 'register')) print(router.urls) # 创建话题(认证成功才能访问 当前用户是谁) router.register(r'topic', topic.TopicView) # # # 我的资讯 router.register(r'news', news.NewsView) # # # 资讯首页 # router.register(r'index', news.IndexView) # # # 收藏 # router.register(r'collect', collect.CollectView) # # # 推荐 # router.register(r'recommend', recommend.RecommendView) # # # 评论 # router.register(r'comment', comment.CommentView) urlpatterns = [ # path('register/', account.RegisterView.as_view({"post": "create"})), path('auth/', account.AuthView.as_view()), ] urlpatterns += router.urls print(urlpatterns)
views/news.py

from django_filters import FilterSet,filters from django_filters.rest_framework import DjangoFilterBackend from rest_framework.viewsets import GenericViewSet from api import models from api.extension.filter import SelfFilterBackend from api.extension.mixins import DigListModelMixin, DigCreateModelMixin from api.serializers.news import NewsSerializer from api.extension.throttle import NewsCreateRateThrottle class NewsFilterSet(FilterSet): # 和topic的一样的分页查询 latest_id = filters.NumberFilter(field_name='id', lookup_expr='lt') class Meta: model = models.News fields = ["latest_id", ] class NewsView(DigListModelMixin, DigCreateModelMixin,GenericViewSet): filter_backends = [SelfFilterBackend, DjangoFilterBackend] filterset_class = NewsFilterSet # 未删除 & 属于当前用户创建的新闻资讯 queryset = models.News.objects.filter(deleted=False).order_by('-id') # 序列化(创建资讯第一步先用户请求数据校验) serializer_class = NewsSerializer # 自定义的类变量 (因为这也就只会实例化一次对象,如果把对象放到get_throttles中 就会每次调用此方法都会实例化一次对象) throttle_objects = [NewsCreateRateThrottle] # drf中 # create 序列化器保存数据的方法 # perform_create调用保存数据的方法 该方法就是一个serializer.save(),本质上就是调用create # 非read_only的字段都得提交 # 创建资讯第二步 保存数据 def perform_create(self, serializer): # 1.创建新闻资讯 # 2.自己对自己的内容做推荐 # - 推荐数量+1 # - 推荐记录 用户&资讯 # 将用户信息保存到数据库 保存前会先去执行serializer中的create方法 serializer.save(user=self.request.user) # 数据库中已经增加成功 调用限流类中的哪个done方法 for throttle in self.get_throttles(): throttle.done() # 限流需要注意的点 # 问题一 drf限流组件只支持每分钟或每小时几次 不支持几分钟几次只能重写此方法(限流类重写parse_rate方法) # 这次创建成功后,下在再创建的时候需要限流 # 限流类应用到视图中 # 只希望再创建资讯的时候有限流也就是发post请求的时候 别的没有 def get_throttles(self): if self.request.method == "POST": return self.throttle_objects return []
serializers/news.py

from rest_framework import serializers from rest_framework.exceptions import ValidationError from api import models class NewsSerializer(serializers.ModelSerializer): image_list = serializers.SerializerMethodField(read_only=True) topic_title = serializers.CharField(source="topic.title", read_only=True) # source="topic.title" 主动跨表 zone_title = serializers.CharField(source="get_zone_display", read_only=True) # source="get_zone_display"获取中文名 status = serializers.CharField(source="get_status_display", read_only=True) # 创建资讯需要提交的字段(write_only的) # title、url、image、'topic', "zone" # write_only只做数据校验read_only只做序列化 # 创建资讯分为几种情况 # - 只有title,只创建文本 + 分区不能是图片 # - 有title,image, # - 有title,url class Meta: model = models.News fields = ['id', "title", "url", 'image', 'topic', "zone", "zone_title", 'image_list', "topic_title", 'collect_count', 'recommend_count', 'comment_count', "status"] read_only_fields = ['collect_count', 'recommend_count', 'comment_count'] extra_kwargs = { 'topic': {'write_only': True}, # 新增时,topic=1 'image': {'write_only': True}, # 图片地址 xxxx,xxxx,xxxx 'zone': {'write_only': True}, } def get_image_list(self, obj): if not obj.image: return [] return obj.image.split(',') def validate_topic(self, value): # 话题可以为空 没有选话题就返回空 if not value: return value # 注意点 # 视图函数里想要获取request 本身就会把 request当参数传递 具体方法没有的话也可以用self.request来获取 # 在serializers中获取request没有self.request request = self.context['request'] # 话题必须时存在还是自己的 exists = models.Topic.objects.filter(deleted=False, id=value.id, user=request.user).exists() if not exists: raise ValidationError("话题不存在") return value def validate_title(self, value): # 自定义钩子函数 对title进行验证 url = self.initial_data.get('url') image = self.initial_data.get('image') zone = self.initial_data.get('zone') if url and image: raise ValidationError("请求数据错误") if not url and not image: if zone == 3: raise ValidationError("分区选择错误") return value def create(self, validated_data): request = self.context["request"] # 1.创建新闻资讯 recommend_count=1 推荐数默认0 new_object = models.News.objects.create(recommend_count=1, **validated_data) # 2.推荐记录 models.Recommend.objects.create( # 当前新闻和用户 news=new_object, # 当前用户 user=request.user ) return new_object
throttle.py

from django_filters import FilterSet,filters from django_filters.rest_framework import DjangoFilterBackend from rest_framework.viewsets import GenericViewSet from api import models from api.extension.filter import SelfFilterBackend from api.extension.mixins import DigListModelMixin, DigCreateModelMixin from api.serializers.news import NewsSerializer from api.extension.throttle import NewsCreateRateThrottle class NewsFilterSet(FilterSet): # 和topic的一样的分页查询 latest_id = filters.NumberFilter(field_name='id', lookup_expr='lt') class Meta: model = models.News fields = ["latest_id", ] class NewsView(DigListModelMixin, DigCreateModelMixin,GenericViewSet): filter_backends = [SelfFilterBackend, DjangoFilterBackend] filterset_class = NewsFilterSet # 未删除 & 属于当前用户创建的新闻资讯 queryset = models.News.objects.filter(deleted=False).order_by('-id') # 序列化(创建资讯第一步先用户请求数据校验) serializer_class = NewsSerializer # 自定义的类变量 (因为这也就只会实例化一次对象,如果把对象放到get_throttles中 就会每次调用此方法都会实例化一次对象) throttle_objects = [NewsCreateRateThrottle] # drf中 # create 序列化器保存数据的方法 # perform_create调用保存数据的方法 该方法就是一个serializer.save(),本质上就是调用create # 非read_only的字段都得提交 # 创建资讯第二步 保存数据 def perform_create(self, serializer): # 1.创建新闻资讯 # 2.自己对自己的内容做推荐 # - 推荐数量+1 # - 推荐记录 用户&资讯 # 将用户信息保存到数据库 保存前会先去执行serializer中的create方法 serializer.save(user=self.request.user) # 数据库中已经增加成功 调用限流类中的哪个done方法 for throttle in self.get_throttles(): throttle.done() # 限流需要注意的点 # 问题一 drf限流组件只支持每分钟或每小时几次 不支持几分钟几次只能重写此方法(限流类重写parse_rate方法) # 这次创建成功后,下在再创建的时候需要限流 # 限流类应用到视图中 # 只希望再创建资讯的时候有限流也就是发post请求的时候 别的没有 def get_throttles(self): if self.request.method == "POST": return self.throttle_objects return []
settings.py

CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "PASSWORD": "qwe123", } } }
4、首页
api/urls

from django.urls import path, include from rest_framework import routers from api.views import account, topic, news # # 以前写路由 # urlpatterns = [ # # /api/register/ 用户访问注册 执行RegisterView视图 注册需要输入用户名密码 用post形式提交 # path('register/', account.RegisterView.as_view({"post": "create"})), # ] # # # drf中用SimpleRouter # router = routers.SimpleRouter() # # /api/register/ 用户访问注册 # # basename='x'也可以为None不写 # # x-list 生成的列表页面 根据这个name可以反向生成url 源码里有体现 # # x-create 生成的添加页面 # # (运行程序可能会出错 )需要在视图里RegisterView 定义queryset # router.register(r'register', account.RegisterView,'x') # urlpatterns = [ # # 扩展url # ] # urlpatterns += router.urls # 通过以上就有了下面 router = routers.SimpleRouter() # 注册/api/register/ 访问注册视图RegisterView 需要post方法 router.register(r'register', account.RegisterView, 'register') # print(router.register(r'register', account.RegisterView, 'register')) print(router.urls) # 创建话题(认证成功才能访问 当前用户是谁) router.register(r'topic', topic.TopicView) # # # 我的资讯 router.register(r'news', news.NewsView) # # # 资讯首页 router.register(r'index', news.IndexView) # # # 收藏 # router.register(r'collect', collect.CollectView) # # # 推荐 # router.register(r'recommend', recommend.RecommendView) # # # 评论 # router.register(r'comment', comment.CommentView) urlpatterns = [ # path('register/', account.RegisterView.as_view({"post": "create"})), path('auth/', account.AuthView.as_view()), ] urlpatterns += router.urls print(urlpatterns)
views/news.py

from django_filters import FilterSet,filters from django_filters.rest_framework import DjangoFilterBackend from rest_framework.viewsets import GenericViewSet from api import models from api.extension.filter import SelfFilterBackend from api.extension.mixins import DigListModelMixin, DigCreateModelMixin from api.serializers.news import NewsSerializer ,IndexSerializer from api.extension.throttle import NewsCreateRateThrottle from api.extension.auth import UserAnonTokenAuthentication class NewsFilterSet(FilterSet): # 和topic的一样的分页查询 latest_id = filters.NumberFilter(field_name='id', lookup_expr='lt') class Meta: model = models.News fields = ["latest_id", ] class NewsView(DigListModelMixin, DigCreateModelMixin,GenericViewSet): # 使用filter_backends 搜索过滤 filter_backends = [SelfFilterBackend, DjangoFilterBackend] # 要使用 FilterSet 启用过滤,请将其添加到视图类的 filterset_class 参数中。 filterset_class = NewsFilterSet # 未删除 & 属于当前用户创建的新闻资讯 queryset = models.News.objects.filter(deleted=False).order_by('-id') # 序列化(创建资讯第一步先用户请求数据校验) serializer_class = NewsSerializer # 自定义的类变量 (因为这也就只会实例化一次对象,如果把对象放到get_throttles中 就会每次调用此方法都会实例化一次对象) throttle_objects = [NewsCreateRateThrottle] # drf中 # create 序列化器保存数据的方法 # perform_create调用保存数据的方法 该方法就是一个serializer.save(),本质上就是调用create # 非read_only的字段都得提交 # 创建资讯第二步 保存数据 def perform_create(self, serializer): # 1.创建新闻资讯 # 2.自己对自己的内容做推荐 # - 推荐数量+1 # - 推荐记录 用户&资讯 # 将用户信息保存到数据库 保存前会先去执行serializer中的create方法 serializer.save(user=self.request.user) # 数据库中已经增加成功 调用限流类中的哪个done方法 for throttle in self.get_throttles(): throttle.done() # 限流需要注意的点 # 问题一 drf限流组件只支持每分钟或每小时几次 不支持几分钟几次只能重写此方法(限流类重写parse_rate方法) # 这次创建成功后,下在再创建的时候需要限流 # 限流类应用到视图中 # 只希望再创建资讯的时候有限流也就是发post请求的时候 别的没有 def get_throttles(self): if self.request.method == "POST": return self.throttle_objects return [] class IndexFilterSet(FilterSet): latest_id = filters.NumberFilter(field_name='id', lookup_expr='lt') class Meta: model = models.News fields = ["latest_id", 'zone'] # ?zone=1 # ?latest_id=99&limit=10 class IndexView(DigListModelMixin,GenericViewSet): # 首页筛选数据 filter_backends = [DjangoFilterBackend] filterset_class = IndexFilterSet # 此视图添加一一个认证类 此认证类有判断登录和不登陆 authentication_classes = [UserAnonTokenAuthentication, ] # 实际得有人在后台审核 status=2 审核通过才会查到 # queryset = models.News.objects.filter(deleted=False, status=2).order_by('-id') # 暂时用这个 展示所有的新闻数据 queryset = models.News.objects.filter(deleted=False).order_by('-id') serializer_class = IndexSerializer
serializers/news.py(点赞和收藏在登录有未登录展示的状态)

from rest_framework import serializers from rest_framework.exceptions import ValidationError from api import models class NewsSerializer(serializers.ModelSerializer): image_list = serializers.SerializerMethodField(read_only=True) topic_title = serializers.CharField(source="topic.title", read_only=True) # source="topic.title" 主动跨表 zone_title = serializers.CharField(source="get_zone_display", read_only=True) # source="get_zone_display"获取中文名 status = serializers.CharField(source="get_status_display", read_only=True) # 创建资讯需要提交的字段(write_only的) # title、url、image、'topic', "zone" # write_only只做数据校验read_only只做序列化 # 创建资讯分为几种情况 # - 只有title,只创建文本 + 分区不能是图片 # - 有title,image, # - 有title,url class Meta: model = models.News fields = ['id', "title", "url", 'image', 'topic', "zone", "zone_title", 'image_list', "topic_title", 'collect_count', 'recommend_count', 'comment_count', "status"] read_only_fields = ['collect_count', 'recommend_count', 'comment_count'] extra_kwargs = { 'topic': {'write_only': True}, # 新增时,topic=1 'image': {'write_only': True}, # 图片地址 xxxx,xxxx,xxxx 'zone': {'write_only': True}, } def get_image_list(self, obj): if not obj.image: return [] return obj.image.split(',') def validate_topic(self, value): # 话题可以为空 没有选话题就返回空 if not value: return value # 注意点 # 视图函数里想要获取request 本身就会把 request当参数传递 具体方法没有的话也可以用self.request来获取 # 在serializers中获取request没有self.request request = self.context['request'] # 话题必须时存在还是自己的 exists = models.Topic.objects.filter(deleted=False, id=value.id, user=request.user).exists() if not exists: raise ValidationError("话题不存在") return value def validate_title(self, value): # 自定义钩子函数 对title进行验证 url = self.initial_data.get('url') image = self.initial_data.get('image') zone = self.initial_data.get('zone') if url and image: raise ValidationError("请求数据错误") if not url and not image: if zone == 3: raise ValidationError("分区选择错误") return value def create(self, validated_data): request = self.context["request"] # 1.创建新闻资讯 recommend_count=1 推荐数默认0 new_object = models.News.objects.create(recommend_count=1, **validated_data) # 2.推荐记录 models.Recommend.objects.create( # 当前新闻和用户 news=new_object, # 当前用户 user=request.user ) return new_object class IndexSerializer(serializers.ModelSerializer): image_list = serializers.SerializerMethodField() # 收藏和点赞 collect = serializers.SerializerMethodField() recommend = serializers.SerializerMethodField() class Meta: model = models.News fields = ['id', "title", "url", 'image_list', 'topic', "zone", "user", 'collect', 'recommend', 'comment_count', ] def get_image_list(self, obj): if not obj.image: return [] return obj.image.split(',') # 收藏 def get_collect(self, obj): request = self.context['request'] # 用户没登陆 if not request.user: return {'count': obj.collect_count, 'has_collect': False} # 查看用户收藏表查询是否存在收藏过次资讯 exists = models.Collect.objects.filter(user=request.user, news=obj).exists() return {'count': obj.collect_count, 'has_collect': exists} # 点赞 def get_recommend(self, obj): request = self.context['request'] if not request.user: return {'count': obj.recommend_count, 'has_recommend': False} exists = models.Recommend.objects.filter(user=request.user, news=obj).exists() return {'count': obj.recommend_count, 'has_recommend': exists}
5、收藏和点赞
api/urls

from django.urls import path, include from rest_framework import routers from api.views import account, topic, news, collect, recommend, comment # # 以前写路由 # urlpatterns = [ # # /api/register/ 用户访问注册 执行RegisterView视图 注册需要输入用户名密码 用post形式提交 # path('register/', account.RegisterView.as_view({"post": "create"})), # ] # # # drf中用SimpleRouter # router = routers.SimpleRouter() # # /api/register/ 用户访问注册 # # basename='x'也可以为None不写 # # x-list 生成的列表页面 根据这个name可以反向生成url 源码里有体现 # # x-create 生成的添加页面 # # (运行程序可能会出错 )需要在视图里RegisterView 定义queryset # router.register(r'register', account.RegisterView,'x') # urlpatterns = [ # # 扩展url # ] # urlpatterns += router.urls # 通过以上就有了下面 router = routers.SimpleRouter() # 注册/api/register/ 访问注册视图RegisterView 需要post方法 router.register(r'register', account.RegisterView, 'register') # print(router.register(r'register', account.RegisterView, 'register')) print(router.urls) # 创建话题(认证成功才能访问 当前用户是谁) router.register(r'topic', topic.TopicView) # # # 我的资讯 router.register(r'news', news.NewsView) # # # 资讯首页 router.register(r'index', news.IndexView) # # # 收藏 router.register(r'collect', collect.CollectView) # # # 推荐 router.register(r'recommend', recommend.RecommendView) # # # 评论 router.register(r'comment', comment.CommentView) urlpatterns = [ # path('register/', account.RegisterView.as_view({"post": "create"})), path('auth/', account.AuthView.as_view()), ] urlpatterns += router.urls print(urlpatterns)
views/collect.py

from rest_framework.viewsets import GenericViewSet from rest_framework.response import Response from django_filters import FilterSet, filters from django_filters.rest_framework import DjangoFilterBackend from api import models from api.serializers.collect import CollectSerializer from api.extension.filter import SelfFilterBackend from api.extension.mixins import DigCreateModelMixin, DigListModelMixin from api.extension import return_code class CollectFilterSet(FilterSet): latest_id = filters.NumberFilter(field_name='id', lookup_expr='lt') class Meta: model = models.Collect fields = ["latest_id", ] # 收藏 DigCreateModelMixin(收藏或取消收藏) DigListModelMixin 我的收藏列表 class CollectView(DigCreateModelMixin, DigListModelMixin, GenericViewSet): """ 收藏接口 """ filter_backends = [SelfFilterBackend, DjangoFilterBackend] # 分页配合进行分页查询 filterset_class = CollectFilterSet # 当前登录用户(SelfFilterBackend)的所有收藏记录 queryset = models.Collect.objects # 分页查询 serializer_class = CollectSerializer def perform_create(self, serializer): user = self.request.user # 先去查当前用户有没有对此资讯 进行收藏 instance = models.Collect.objects.filter(user=user, **serializer.validated_data).first() # 没有收藏 if not instance: # 增加收藏记录 instance = serializer.save(user=user) instance.news.collect_count += 1 instance.news.save() # 'active': True 前端点亮 return Response({"code": return_code.SUCCESS, 'data': {'active': True}}) else: instance.delete() instance.news.collect_count -= 1 instance.news.save() return Response({"code": return_code.SUCCESS, 'data': {'active': False}})
views/recomment.py

from rest_framework.viewsets import GenericViewSet from rest_framework.response import Response from django_filters import FilterSet, filters from django_filters.rest_framework import DjangoFilterBackend from api import models from api.serializers.recommend import RecommendSerializer from api.extension.filter import SelfFilterBackend from api.extension.mixins import DigCreateModelMixin, DigListModelMixin from api.extension import return_code class RecommendFilterSet(FilterSet): latest_id = filters.NumberFilter(field_name='id', lookup_expr='lt') class Meta: model = models.Recommend fields = ["latest_id", ] class RecommendView(DigCreateModelMixin, DigListModelMixin, GenericViewSet): """ 推荐接口 """ filter_backends = [SelfFilterBackend, DjangoFilterBackend] filterset_class = RecommendFilterSet queryset = models.Recommend.objects serializer_class = RecommendSerializer def perform_create(self, serializer): user = self.request.user instance = models.Recommend.objects.filter(user=user, **serializer.validated_data).first() if not instance: instance = serializer.save(user=user) instance.news.recommend_count += 1 instance.news.save() return Response({"code": return_code.SUCCESS, 'data': {'active': True}}) else: instance.delete() instance.news.recommend_count -= 1 instance.news.save() return Response({"code": return_code.SUCCESS, 'data': {'active': False}})
serializers/collect.py

from rest_framework import serializers from rest_framework.exceptions import ValidationError from api import models class CollectSubNewsSerializer(serializers.ModelSerializer): image_list = serializers.SerializerMethodField(read_only=True) class Meta: model = models.News fields = ['id', 'title', 'url', "image_list"] def get_image_list(self, obj): if not obj.image: return [] return obj.image.split(',') class CollectSerializer(serializers.ModelSerializer): news_info = CollectSubNewsSerializer(read_only=True, source="news") # news=2 class Meta: model = models.Collect fields = ['id', "news", "news_info"] extra_kwargs = {'news': {'write_only': True}} def validate_news(self, value): if value.deleted: raise ValidationError("资讯不存在") return value
serializers/recomment.py

from rest_framework import serializers from rest_framework.exceptions import ValidationError from api import models class RecommendSubNewsSerializer(serializers.ModelSerializer): image_list = serializers.SerializerMethodField(read_only=True) class Meta: model = models.News fields = ['id', 'title', 'url', "image_list"] def get_image_list(self, obj): if not obj.image: return [] return obj.image.split(',') class RecommendSerializer(serializers.ModelSerializer): news_info = RecommendSubNewsSerializer(read_only=True, source="news") class Meta: model = models.Recommend fields = ['id', "news", "news_info"] extra_kwargs = {'news': {'write_only': True}}
6、评论
api/urls

from django.urls import path, include from rest_framework import routers from api.views import account, topic, news, collect, recommend, comment # # 以前写路由 # urlpatterns = [ # # /api/register/ 用户访问注册 执行RegisterView视图 注册需要输入用户名密码 用post形式提交 # path('register/', account.RegisterView.as_view({"post": "create"})), # ] # # # drf中用SimpleRouter # router = routers.SimpleRouter() # # /api/register/ 用户访问注册 # # basename='x'也可以为None不写 # # x-list 生成的列表页面 根据这个name可以反向生成url 源码里有体现 # # x-create 生成的添加页面 # # (运行程序可能会出错 )需要在视图里RegisterView 定义queryset # router.register(r'register', account.RegisterView,'x') # urlpatterns = [ # # 扩展url # ] # urlpatterns += router.urls # 通过以上就有了下面 router = routers.SimpleRouter() # 注册/api/register/ 访问注册视图RegisterView 需要post方法 router.register(r'register', account.RegisterView, 'register') # print(router.register(r'register', account.RegisterView, 'register')) print(router.urls) # 创建话题(认证成功才能访问 当前用户是谁) router.register(r'topic', topic.TopicView) # # # 我的资讯 router.register(r'news', news.NewsView) # # # 资讯首页 router.register(r'index', news.IndexView) # # # 收藏 router.register(r'collect', collect.CollectView) # # # 推荐 router.register(r'recommend', recommend.RecommendView) # # # 评论 router.register(r'comment', comment.CommentView) urlpatterns = [ # path('register/', account.RegisterView.as_view({"post": "create"})), path('auth/', account.AuthView.as_view()), ] urlpatterns += router.urls print(urlpatterns)
views/comment.py

import datetime from rest_framework.request import Request from rest_framework.viewsets import GenericViewSet from rest_framework.permissions import BasePermission from rest_framework.exceptions import PermissionDenied from django_filters import FilterSet, filters from django_filters.rest_framework import DjangoFilterBackend from api import models from api.serializers.comment import CreateCommentSerializer, ListCommentSerializer from api.extension.filter import SelfFilterBackend from api.extension.mixins import DigCreateModelMixin, DigListModelMixin, DigDestroyModelMixin, DigUpdateModelMixin from api.extension.auth import UserAnonTokenAuthentication, TokenAuthentication from api.extension import return_code class CommentFilterSet(FilterSet): news = filters.NumberFilter(field_name='news', required=True) latest_id = filters.DateTimeFilter(field_name='descendant_update_datetime', lookup_expr='lte') class Meta: model = models.Comment fields = ["latest_id", 'news'] """ 获取评论列表DigListModelMixin 1.先根据后代的更新时间,进行排序,获取根评论 10。 2.这些根评论关联的子评论,并构造父子关系。 """ class CommentView(DigListModelMixin, DigCreateModelMixin, GenericViewSet): """ 评论 """ # filter_backends = [DjangoFilterBackend] # 分页获取列表数据 filterset_class = CommentFilterSet # 新增品论 # 认证通过后才能评论(只有添加评论的时候需要) authentication_classes = [TokenAuthentication, ] # 评论列表 # ?news=2 -> news=2 # 获取 某条 新闻资讯的 depth=0根评论(根据后代更新时间排序) queryset = models.Comment.objects.filter(depth=0).order_by("-descendant_update_datetime") # 新增评论 # 有钩子方法get_serializer_class进行校验 serializer_class = CreateCommentSerializer # 新增校验成功后 要进行保存了 def perform_create(self, serializer): # 拿回复评论 reply = serializer.validated_data.get('reply') # 如果没有回复就是根 if not reply: # 如果是根评论 'news', "reply", "content" instance = serializer.save(user=self.request.user) else: # 如果子评论 # 1.获取根评论(根评论不是空的) if not reply.root: # 给根评论回复 root = reply else: root = reply.root # 创建评论 instance = serializer.save(user=self.request.user, depth=reply.depth + 1, root=root) # 根评论的最新更新时间 root.descendant_update_datetime = datetime.datetime.now() root.save() instance.news.comment_count += 1 instance.news.save() def get_serializer_class(self): # 如果是get请求(查看评论列表)用ListCommentSerializer校验 if self.request.method == "GET": return ListCommentSerializer # post请求 return CreateCommentSerializer def get_authenticators(self): # 如果是 post请求(新增评论)就用认证类authentication_classe if self.request.method == "POST": return super().get_authenticators() return []
serializers/comment.py

from rest_framework import serializers from api import models class CreateCommentSerializer(serializers.ModelSerializer): create_datetime = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", read_only=True) # 'news', "reply", "content" # 根评论:news(哪条新闻)、content(内容),reply=null(没给别人回复),depth=0(评论深度),root=null(跟评论) # 子评论:news、content,reply=回复的评论ID,depth=回复的评论深度+1,root=读回复的评论root=null或depth=0 ==读回复的评论;===读回复的评论.root # descendant_update_datetime根评论最近的更新时间; class Meta: model = models.Comment fields = ['news', "reply", "content", 'depth', "create_datetime"] read_only_fields = ['depth', ] extra_kwargs = {'news': {'write_only': True}} class ListCommentSerializer(serializers.ModelSerializer): create_datetime = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S") children = serializers.SerializerMethodField() class Meta: model = models.Comment fields = ['create_datetime', "reply", "content", 'children'] def get_children(self, obj): # 获取当前根评论的所有的子孙评论(后代) descendant_queryset = models.Comment.objects.filter(root=obj).order_by('id') # 维护一个评论字典 descendant_dict = {} """ { # 评论的层级 根评论id=2 # 1级评论 获取到了所有的子评论 11:{"reply": 2, children:[ # 13-> id=13回复的 {"reply": 11, children:[ # 15-> {"reply": 13, children:[ # 16-> {"reply": 15, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 4, "create_datetime": "2021-09-01 22:32:22"} ],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 3, "create_datetime": "2021-09-01 22:32:22"} ],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 2, "create_datetime": "2021-09-01 22:32:22"} ],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 1, "create_datetime": "2021-09-01 22:32:22"} 12:{"reply": 2, children:[ # 14-> id=14回复的 {"reply": 12, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 2, "create_datetime": "2021-09-01 22:32:22"} ],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 1, "create_datetime": "2021-09-01 22:32:22"} # 回复的是id=11的评论 13:{"reply": 11, children:[ 15:{"reply": 13, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 3, "create_datetime": "2021-09-01 22:32:22"} ],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 2, "create_datetime": "2021-09-01 22:32:22"} # id=14回复的是id=12的评论 14:{"reply": 12, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 2, "create_datetime": "2021-09-01 22:32:22"} 15:{"reply": 13, children:[ 16:{"reply": 15, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 4, "create_datetime": "2021-09-01 22:32:22"} ],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 3, "create_datetime": "2021-09-01 22:32:22"} 16:{"reply": 15, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 4, "create_datetime": "2021-09-01 22:32:22"} } """ # 对所有的评论分级(子孙评论都杂糅在一起了) # 循环所有子孙 for descendant in descendant_queryset: ser = CreateCommentSerializer(instance=descendant, many=False) # 在每个评论里放一个children 也就是次评论的子评论 row = {'children': []} row.update(ser.data) # 通过序列化器CreateCommentSerializer # 得到一个字典fields = ['news', "reply", "content", 'depth', "create_datetime"] descendant_dict[descendant.id] = row # 根评论obj的1级评论(能获取到此根品论的所有子级品论如上面的例子) children_list = [ # 这里放的是1级评论的内存地址(多级评论内存地址的巧妙运用) # # 11 # {"reply": 2, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 1, "create_datetime": "2021-09-01 22:32:22"}, # # 12 # {"reply": 2, children:[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 1, "create_datetime": "2021-09-01 22:32:22"} ] # 循环descendant_dict字典 for cid, item in descendant_dict.items(): depth = item['depth'] # 如果深度是1 if depth == 1: # 将数据添加到children_list列表中 children_list.append(item) continue # reply_id = item['reply'] # 回复哪条评论的id reply_id descendant_dict[reply_id]['children'].append(item) return children_list
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具