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

 

 

 

 
posted @ 2022-07-10 20:06  silencio。  阅读(91)  评论(0编辑  收藏  举报