• dig


    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"'
    api/extension/auth.py
    from rest_framework.filters import BaseFilterBackend
    
    
    class SelfFilterBackend(BaseFilterBackend):
        def filter_queryset(self, request, queryset, view):
            return queryset.filter(user=request.user)
    api/extension/filter.py
    from rest_framework import mixins
    from rest_framework.response import Response
    from api.extension import return_code
    
    
    class DigCreateModelMixin(mixins.CreateModelMixin):
        def create(self, request, *args, **kwargs):
            serializer = self.get_serializer(data=request.data)
            # 1. 异常处理
            if not serializer.is_valid():
                return Response({"code": return_code.VALIDATE_ERROR, 'detail': serializer.errors})
            # 2. 优化perform_create
            res = self.perform_create(serializer)
            # 3. 返回数据的处理
            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})
    api/extension/mixins.py
    from rest_framework.pagination import LimitOffsetPagination
    
    
    class DigLimitOffsetPagination(LimitOffsetPagination):
        # xxxxxx?limit=10
        default_limit = 10
        max_limit = 100
        offset_query_param = None
    api/extension/page.py
    # 成功
    SUCCESS = 0
    
    # 用户提交数据校验失败
    VALIDATE_ERROR = 1001
    
    # 认证失败
    AUTH_FAILED = 2000
    
    # 认证过期
    AUTH_OVERDUE = 2001
    
    # 无权访问
    PERMISSION_DENIED = 3000
    
    
    # 无权访问
    TOO_MANY_REQUESTS = 4000
    api/extension/return_code.py
    from rest_framework import status
    from rest_framework.throttling import SimpleRateThrottle
    from django.core.cache import cache as default_cache
    from rest_framework import exceptions
    from api.extension import return_code
    
    
    class ThrottledException(exceptions.APIException):
        status_code = status.HTTP_429_TOO_MANY_REQUESTS
        default_code = 'throttled'
    
    
    class NewsCreateRateThrottle(SimpleRateThrottle):
        cache = default_cache  # 访问记录存放在django的缓存中(需设置缓存)
        scope = "user"  # 构造缓存中的key
        cache_format = 'throttle_%(scope)s_%(ident)s'
    
        # 设置访问频率,例如:1/5m
        THROTTLE_RATES = {"user": "1/5m"}
    
        def parse_rate(self, rate):
            if rate is None:
                return (None, None)
            num, period = rate.split('/')  # "1/5m"
            num_requests = int(num)
            duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[-1]]
            count = int(period[0:-1])
            return (num_requests, duration * count)
    
        def get_cache_key(self, request, view):
            ident = request.user.pk
            return self.cache_format % {'scope': self.scope, 'ident': ident}
    
        def throttle_failure(self):
            wait = self.wait()
            detail = {
                "code": return_code.TOO_MANY_REQUESTS,
                "data": "访问频率限制",
                'detail': "需等待{}秒后才能访问".format(int(wait))
            }
            raise ThrottledException(detail)
    
        def throttle_success(self):
            # self.history.insert(0, self.now)
            # self.cache.set(self.key, self.history, self.duration)
            return True
    
        def done(self):
            """ 数据库中创建成功后,再来调用这个方法"""
            self.history.insert(0, self.now)
            self.cache.set(self.key, self.history, self.duration)
    api/extension/throttle.py
    from rest_framework import serializers
    from rest_framework.exceptions import ValidationError
    from api import models
    
    
    class RegisterSerializer(serializers.ModelSerializer):
        confirm_password = serializers.CharField(label="确认密码", min_length=8, write_only=True)
        password = serializers.CharField(label="密码", min_length=8, write_only=True)
    
        class Meta:
            model = models.UserInfo
            fields = ['username', "phone", "password", "confirm_password"]
    
        def validate_username(self, value):
            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)  # 不提交
        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
    api/serializers/account.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
    api/serializers/collect.py
    from rest_framework import serializers
    from api import models
    
    
    """
    根评论
    {
        "news":1,
        "content":"11111111111111111",
        "reply":null
    }
    
    
    子评论
    {
        "news":1,
        "content":"3.1111",
        "reply":3
    }
    """
    
    
    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 = {}
            """
            {
                11:{"reply": 2, children:[
                    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->{"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"}
                
                
                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"}
                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)
                row = {'children': []}
                row.update(ser.data)
                descendant_dict[descendant.id] = row
    
            print(descendant_dict)
            # 根评论obj的1级评论
            children_list = [
                # # 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"}
            ]
            for cid, item in descendant_dict.items():
                depth = item['depth']
                if depth == 1:
                    children_list.append(item)
                    continue
                reply_id = item['reply']
                descendant_dict[reply_id]['children'].append(item)
    
            return children_list
    api/serializers/comment.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)
        zone_title = serializers.CharField(source="get_zone_display", read_only=True)
        status = serializers.CharField(source="get_status_display", read_only=True)
    
        # title、url、image、'topic', "zone"
        #   - 只有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 = 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):
            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.创建新闻资讯
            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 IndexSubTopicSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Topic
            fields = ['id', 'title', 'is_hot']
    
    
    class IndexSubUserSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.UserInfo
            fields = ['id', 'username', ]
    
    
    class IndexSerializer(serializers.ModelSerializer):
        image_list = serializers.SerializerMethodField()
    
        collect = serializers.SerializerMethodField()
        recommend = serializers.SerializerMethodField()
    
        zone = serializers.CharField(source='get_zone_display')
        topic = IndexSubTopicSerializer(read_only=True)
        user = IndexSubUserSerializer(read_only=True)
    
        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}
    api/serializers/news.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}}
    api/serializers/recommend.py
    from rest_framework import serializers
    from rest_framework.exceptions import ValidationError
    from api import models
    
    
    class TopicSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Topic
            fields = ['id', "title", "is_hot"]
            # read_only_fields = ['is_hot']
            extra_kwargs = {'is_hot': {'read_only': True}}
    api/serializers/topic.py
    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.mixins import DigCreateModelMixin
    from api.serializers.account import RegisterSerializer, AuthSerializer
    from api.extension import return_code
    from api import models
    
    from rest_framework.throttling import SimpleRateThrottle
    
    from rest_framework.mixins import CreateModelMixin
    
    """
    1. 只需要提供POST方法
    2. 请求进来执行 DigCreateModelMixin的create方法
    3. 获取数据request.data,进行校验(RegisterSerializer)
    """
    
    
    class RegisterView(DigCreateModelMixin, GenericViewSet):
        """ 用户注册 """
    
        authentication_classes = []
        permission_classes = []
        serializer_class = RegisterSerializer
    
        def perform_create(self, serializer):
            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')
    
            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 = 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}})
    api/views/account.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", ]
    
    
    class CollectView(DigCreateModelMixin, DigListModelMixin, GenericViewSet):
        """ 收藏接口 """
        filter_backends = [SelfFilterBackend, DjangoFilterBackend]
        filterset_class = CollectFilterSet
    
        # 当前登录用户的所有收藏记录
        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()
                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}})
    api/views/collect.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']
    
    
    
    """
    1.先根据后代的更新时间,进行排序,获取根评论 10。
    2.这些根评论关联的子评论,并构造父子关系。
    """
    class CommentView(DigListModelMixin, DigCreateModelMixin, GenericViewSet):
        """ 评论 """
        filter_backends = [DjangoFilterBackend]
        filterset_class = CommentFilterSet
    
        authentication_classes = [TokenAuthentication, ]
    
        # ?news=2  ->  news=2
        # 获取某条新闻资讯的 根评论(根据后代更新时间排序)
        queryset = models.Comment.objects.filter(depth=0).order_by("-descendant_update_datetime")
        serializer_class = CreateCommentSerializer
    
        def perform_create(self, serializer):
            print(serializer.initial_data)  #{'news': 1, 'content': '...', 'reply': 2}
            print(serializer.validated_data) # OrderedDict([('news', <News: News object (1)>), ('reply', <Comment: Comment object (2)>), ('content', '...')])
            reply = serializer.validated_data.get('reply')  #reply是对象
    
            if not reply:
                # 如果是根评论 'news', "reply", "content"
                """
                {"news":1,
                 "content":"111",
                 "reply":null
                 }
                """
                instance = serializer.save(user=self.request.user)
            else:
                # 如果子评论
                """
                {"news":1,
                 "content":"111",
                 "reply":2
                 }
                """
    
                # 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):
            if self.request.method == "GET":
                return ListCommentSerializer
            return CreateCommentSerializer
    
        def get_authenticators(self):
            if self.request.method == "POST":
                return super().get_authenticators()
            return []
    api/views/comment.py
    from rest_framework.viewsets import GenericViewSet
    from django_filters import FilterSet, filters
    from django_filters.rest_framework import DjangoFilterBackend
    
    from api import models
    from api.serializers.news import NewsSerializer, IndexSerializer
    from api.extension.filter import SelfFilterBackend
    from api.extension.mixins import DigCreateModelMixin, DigListModelMixin
    from api.extension.auth import UserAnonTokenAuthentication
    from api.extension.throttle import NewsCreateRateThrottle
    
    
    class NewsFilterSet(FilterSet):
        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
    
        # 自定义的类变量
        throttle_objects = [NewsCreateRateThrottle(), ]
    
        def perform_create(self, serializer):
            # 1.创建新闻资讯
            # 2.自己对自己的内容做推荐
            #       - 推荐数量+1
            #       - 推荐记录  用户&资讯
            serializer.save(user=self.request.user)
    
            # 数据库中已增加成功,调用限流的那个done方法
            for throttle in self.get_throttles():
                throttle.done()
    
        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, ]
    
        # 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
    api/views/news.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}})
    api/views/recommend.py
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    from rest_framework.views import APIView
    from rest_framework.viewsets import GenericViewSet
    from rest_framework.response import Response
    class TestView(APIView):
        authentication_classes = []
        def post(self, request):
            hobbies=request.data.get('hobbies')
            print(hobbies,type(hobbies))
    
            return Response('ok')
    api/views/test.py
    from rest_framework.viewsets import GenericViewSet
    from django_filters import FilterSet, filters
    from django_filters.rest_framework import DjangoFilterBackend
    
    from api import models
    from api.serializers.topic import TopicSerializer
    from api.extension.filter import SelfFilterBackend
    from api.extension.mixins import DigCreateModelMixin, DigListModelMixin, DigDestroyModelMixin, DigUpdateModelMixin
    
    
    class TopicFilterSet(FilterSet):
        # ?latest_id=99             ->  id<99
        # ?latest_id=99&limit=10    ->  id<99  limit 10
        latest_id = filters.NumberFilter(field_name='id', lookup_expr='lt')
    
        class Meta:
            model = models.Topic
            fields = ["latest_id", ]
    
    
    class TopicView(DigListModelMixin, DigCreateModelMixin, DigDestroyModelMixin, DigUpdateModelMixin, GenericViewSet):
        """ 主题 """
    
        # 当前登录用户的调教
        filter_backends = [SelfFilterBackend, DjangoFilterBackend]
        filterset_class = TopicFilterSet
    
        queryset = models.Topic.objects.filter(deleted=False).order_by('-id')
    
        serializer_class = TopicSerializer
    
        def perform_create(self, serializer):
            serializer.save(user=self.request.user)
    
        def perform_destroy(self, instance):
            instance.deleted = True
            instance.save()
    api/views/topic.py
    from django.apps import AppConfig
    
    
    class ApiConfig(AppConfig):
        default_auto_field = 'django.db.models.BigAutoField'
        name = 'api'
    api/apps.py
    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)
    api/models.py
    from django.urls import path
    from rest_framework import routers
    from api.views import account, topic, news, collect, recommend, comment,test
    
    # urlpatterns = [
    #     # /api/register/
    #     path('register/', account.RegisterView.as_view({"post": "create"})),
    # ]
    
    
    # router = routers.SimpleRouter()
    #
    # # /api/register/
    # #   x-list
    # #   x-create
    # router.register(r'register', account.RegisterView)
    #
    # urlpatterns = [
    #
    # ]
    # urlpatterns += router.urls
    
    router = routers.SimpleRouter()
    router.register(r'register', account.RegisterView, 'register')
    
    # 创建话题(认证)
    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()),
        path('test/', test.TestView.as_view()),
    ]
    
    urlpatterns += router.urls
    api/urls.py
    """
    Django settings for dig project.
    
    Generated by 'django-admin startproject' using Django 3.2.6.
    
    For more information on this file, see
    https://docs.djangoproject.com/en/3.2/topics/settings/
    
    For the full list of settings and their values, see
    https://docs.djangoproject.com/en/3.2/ref/settings/
    """
    import os
    from pathlib import Path
    
    # Build paths inside the project like this: BASE_DIR / 'subdir'.
    BASE_DIR = Path(__file__).resolve().parent.parent
    
    # Quick-start development settings - unsuitable for production
    # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
    
    # SECURITY WARNING: keep the secret key used in production secret!
    SECRET_KEY = 'django-insecure-xi3i8jw=!smfwo%pg$$w1y5a^#wqk)&tc_!oc8_7^jqtf#4r@w'
    
    # SECURITY WARNING: don't run with debug turned on in production!
    DEBUG = True
    
    ALLOWED_HOSTS = []
    
    # Application definition
    
    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'
    ]
    
    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
    
    ROOT_URLCONF = 'dig.urls'
    
    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'templates')]
            ,
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    
    WSGI_APPLICATION = 'dig.wsgi.application'
    
    # Database
    # https://docs.djangoproject.com/en/3.2/ref/settings/#databases
    
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': BASE_DIR / 'db.sqlite3',
        }
    }
    
    # Password validation
    # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
    
    AUTH_PASSWORD_VALIDATORS = [
        {
            'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
        },
        {
            'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
        },
    ]
    
    # Internationalization
    # https://docs.djangoproject.com/en/3.2/topics/i18n/
    
    # LANGUAGE_CODE = 'en-us'
    LANGUAGE_CODE = 'zh-hans'
    
    # datetime.datetime.now() / datetime.datetime.utcnow() => utc时间
    # TIME_ZONE = 'UTC'
    # datetime.datetime.now() - 东八区时间 / datetime.datetime.utcnow() => utc时间
    TIME_ZONE = 'Asia/Shanghai'
    
    USE_I18N = True
    
    USE_L10N = True
    
    # 影响自动生成数据库时间字段;
    #       USE_TZ = True,创建UTC时间写入到数据库。
    #       USE_TZ = False,根据TIME_ZONE设置的时区进行创建时间并写入数据库
    USE_TZ = False
    
    # Static files (CSS, JavaScript, Images)
    # https://docs.djangoproject.com/en/3.2/howto/static-files/
    
    STATIC_URL = '/static/'
    
    # Default primary key field type
    # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
    
    DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
    
    from rest_framework.versioning import AcceptHeaderVersioning
    
    REST_FRAMEWORK = {
        # 版本配置
        "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning",
        "DEFAULT_VERSION": "v1",
        "ALLOWED_VERSIONS": ["v1"],
        "VERSION_PARAM": "version",
        # # 认证配置
        "DEFAULT_AUTHENTICATION_CLASSES": ["api.extension.auth.TokenAuthentication", ],
        "UNAUTHENTICATED_USER": lambda: None,
        "UNAUTHENTICATED_TOKEN": lambda: None,
        # # 分页配置
        "DEFAULT_PAGINATION_CLASS": "api.extension.page.DigLimitOffsetPagination"
    }
    
    
    CACHES = {
        "default": {
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": "redis://127.0.0.1:6379",
            "OPTIONS": {
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
                "PASSWORD": "",
            }
        }
    }
    dig/settings.py
    """dig URL Configuration
    
    The `urlpatterns` list routes URLs to views. For more information please see:
        https://docs.djangoproject.com/en/3.2/topics/http/urls/
    Examples:
    Function views
        1. Add an import:  from my_app import views
        2. Add a URL to urlpatterns:  path('', views.home, name='home')
    Class-based views
        1. Add an import:  from other_app.views import Home
        2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
    Including another URLconf
        1. Import the include() function: from django.urls import include, path
        2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
    """
    from django.contrib import admin
    from django.urls import path, include
    
    urlpatterns = [
        path('api/', include('api.urls')),
    ]
    dig/urls.py
    django-filter==2.4.0
    django-redis==5.0.0
    djangorestframework==3.12.4
    
    
    
    # info  = {
    #             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"},
    #             13:{"reply": 11, "children":[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 2, "create_datetime": "2021-09-01 22:32:22"},
    #             14:{"reply": 12, "children":[],"content": "oooadfa;skdjf;akjsd;flkjasdf","depth": 2, "create_datetime": "2021-09-01 22:32:22"},
    #             15:{"reply": 13, "children":[],"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"}
    #         }
    #
    #
    # data_list = []
    #
    # data_list.append(info[11])
    # data_list.append(info[12])
    #
    # info[11]['children'].append(666)
    #
    # print(data_list)
    
    
    ####################
    
    
    descendant_dict = {
        3: {'children': [], 'reply': 1, 'content': '1-1评论', 'depth': 1, 'create_datetime': '2021-09-01 11:26:04'},
        4: {'children': [], 'reply': 3, 'content': '3-1评论', 'depth': 2, 'create_datetime': '2021-09-01 11:26:16'},
        5: {'children': [], 'reply': 3, 'content': '3-1评论', 'depth': 2, 'create_datetime': '2021-09-01 22:16:40'},
        6: {'children': [], 'reply': 3, 'content': '3-1评论', 'depth': 2, 'create_datetime': '2021-09-01 22:17:02'},
        9: {'children': [], 'reply': 3, 'content': '3-1评论', 'depth': 2, 'create_datetime': '2022-01-03 22:32:08'},
        10: {'children': [], 'reply': 3, 'content': '3-1评论', 'depth': 2, 'create_datetime': '2022-01-03 22:32:38'},
        12: {'children': [], 'reply': 3, 'content': '3.1111', 'depth': 2, 'create_datetime': '2022-01-03 22:40:28'}
    }
    
    children_list = []
    for cid, item in descendant_dict.items():
        depth = item['depth']
        if depth == 1:
            children_list.append(item)
            # continue
        reply_id = item['reply']
        descendant_dict[reply_id]['children'].append(item)
    
    # li = [
    #     {'children': [
    #         {'children': [], 'reply': 3, 'content': '3-1评论', 'depth': 2, 'create_datetime': '2021-09-01 11:26:16'},
    #         {'children': [], 'reply': 3, 'content': '3-1评论', 'depth': 2, 'create_datetime': '2021-09-01 22:16:40'},
    #         {'children': [], 'reply': 3, 'content': '3-1评论', 'depth': 2, 'create_datetime': '2021-09-01 22:17:02'},
    #         {'children': [], 'reply': 3, 'content': '3-1评论', 'depth': 2, 'create_datetime': '2022-01-03 22:32:08'},
    #         {'children': [], 'reply': 3, 'content': '3-1评论', 'depth': 2, 'create_datetime': '2022-01-03 22:32:38'},
    #         {'children': [], 'reply': 3, 'content': '3.1111', 'depth': 2, 'create_datetime': '2022-01-03 22:40:28'}
    #     ],
    #            'reply': 1, 'content': '1-1评论', 'depth': 1, 'create_datetime': '2021-09-01 11:26:04'}
    #     ]
    
    print(children_list)
    View Code
     
  • 相关阅读:
    win8下Source Insight has not been installed completely问题的解决
    linux命令学习——ps
    linux命令学习——file
    树莓派进阶之路 (023)
    树莓派进阶之路 (022)
    树莓派进阶之路 (021)
    树莓派进阶之路 (020)
    ubuntu下安装gedit插件
    C语言学习笔记 (004)
    C语言学习笔记 (003)
  • 原文地址:https://www.cnblogs.com/bubu99/p/16463922.html
Copyright © 2020-2023  润新知