answer_reply_manager.py 9.83 KB
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# 回答评论
from __future__ import unicode_literals, absolute_import, print_function

from itertools import chain, groupby
from operator import itemgetter
from django.db.models import Q, Count
from django.utils.html import escape

from gm_types.gaia import (
    USER_TYPE,
)

from qa.models.answer import (
    AnswerReply,
    AnswerVoteReply,
    AnswerReplyImages,
)
from qa.utils.time import get_timestamp_or_none
from talos.services import (
    UserConvertService,
)
from utils.base_manager import (
    BaseManager,
)


class AnswerReplyManager(BaseManager):
    model = AnswerReply

    @staticmethod
    def reply_is_voted_by_ids(reply_ids, viewer_user_id=None):
        """
        获取回答评论的点赞状态
        :param reply_ids:
        :param viewer_user_id:
        :return:
        """
        result = {}
        if not all([reply_ids, viewer_user_id]):
            return result

        avrs = AnswerVoteReply.objects.filter(
            user=viewer_user_id,
            answerreply_id__in=reply_ids
        ).values_list("answerreply_id", flat=True)

        if avrs:
            result = {av: True for av in avrs}

        return result

    @staticmethod
    def get_reply_images_by_reply_ids(reply_ids):
        """
        通过评论id获取图片
        :param reply_ids:
        :return:
        """
        reply_images = AnswerReplyImages.objects.filter(
            reply_id__in=reply_ids
        ).values("reply_id", "url", "width", "height")

        result = {}
        for reply_id, items in groupby(
                sorted(reply_images, key=itemgetter("reply_id")),
                key=itemgetter("reply_id")
        ):
            result.update({
                reply_id: [{
                    "image_url": item.get("url", ""),
                    "width": item.get("width", 0),
                    "height": item.get("height", 0),
                } for item in items]
            })

        return result

    def get_reply_comment_nums_by_ids(self, reply_ids):
        """
        获取评论的子评论数
        :param reply_ids:
        :return:
        """
        if not reply_ids:
            return {}

        _nums = self.model.objects.filter(
            is_online=True,
            first_reply_id__in=reply_ids
        ).values("first_reply_id").annotate(cnt=Count("first_reply_id")).values("first_reply_id", "cnt")

        return {num["first_reply_id"]: num["cnt"] for num in _nums}

    def get_comments_data(self, reply_map_ids):
        """
        获取评论数据
        :param reply_map_ids:评论映射id列表[(reply_id, commented_id)]
        :return:
        """
        result = {}
        if not list(filter(None, reply_map_ids)):
            return result

        comments_info = self.model.objects.filter(
            pk__in=set(filter(None, chain.from_iterable(reply_map_ids)))
        ).values("id", "content", "user")

        valid_user_ids, comments_info_pre_dic = set(), dict()
        for item in comments_info:
            _data = dict(item)
            _user_id = _data.pop("user", 0)
            _data.update({
                "user_id": _user_id
            })
            valid_user_ids.add(_user_id)
            comments_info_pre_dic.update({
                item["id"]: _data
            })

        # 获取用户信息,这里不需要拿是否关注的状态
        users_info = UserConvertService.get_user_info_by_user_ids(
            user_ids=valid_user_ids,
        )
        # 获取评论图片数据
        comment_images_data = self.get_reply_images_by_reply_ids(
            reply_ids=set(filter(None, map(itemgetter(0), reply_map_ids)))
        )

        for r_id, comment_id in reply_map_ids:
            reply_info = comments_info_pre_dic.get(r_id, {})
            comment_info = comments_info_pre_dic.get(comment_id, {})
            reply_user_info = users_info.get(reply_info.get("user_id", 0), {})
            comment_user_info = users_info.get(comment_info.get("user_id", 0), {})

            if comment_info and reply_user_info.get("user_id", 0) != comment_user_info.get("user_id", 0):
                at_nickname = comment_user_info.get("user_name", "")
            else:
                at_nickname = ""

            if reply_info:
                _data = {
                    'content': escape(reply_info.get("content", "")),
                    "comment_id": r_id,
                    "comment_user_id": reply_user_info.get("user_id", 0),
                    'comment_user_type': USER_TYPE.NORMAL,
                    "comment_user_gm_url": "",  # 占位,在backend处理
                    "nickname": reply_user_info.get("user_name", ""),
                    "at_user_id": comment_user_info.get("user_id", 0),
                    "at_user_gm_url": "",  # 占位,在backend处理
                    "at_user_type": USER_TYPE.NORMAL,
                    "at_nickname": at_nickname,
                    # 补充用户信息
                    "comment_user_info": reply_user_info,
                    "at_user_info": comment_user_info,
                    "images": comment_images_data.get(r_id, []),
                }
                result.update({
                    (r_id, comment_id): _data
                })

        return result

    def get_reply_sub_comments(self, reply_id, need_newest=False, top_comment_id=None, start_num=0, count=2):
        """
        获取评论的子评论,引用时注意传参,默认获取全部的评论(老逻辑),考虑性能问题不要这么整……
        :param reply_id: 评论id
        :param need_newest: 是否需要最新的
        :param top_comment_id: 回答的二级评论置顶
        :param start_num:
        :param count:
        :return: [(id, commented_id)]注意这里只返回映射关系
        """
        if need_newest:
            _order_by = "-id"
        else:
            _order_by = "id"

        comment_map_ids = []

        if top_comment_id and not start_num:
            second_level_comment = self.model.objects.filter(
                id=top_comment_id,
                is_online=True
            ).only("id", "commented_reply_id").first()
            if second_level_comment:
                comment_map_ids.append((second_level_comment.id, second_level_comment.commented_reply_id))
                count -= 1

        comment_map_ids.extend(list(self.model.objects.filter(
                first_reply_id=reply_id,
                is_online=True
            ).order_by(_order_by).values_list(
            "id", "commented_reply_id")[start_num: start_num + count])
        )

        return comment_map_ids

    def get_reply_base_data(self, reply, need_sub_comments=False, need_newest=False, top_comment_id=None):
        """
        获取评论信息
        :param reply:
        :param need_sub_comments: 是否取子评论
        :param need_newest: 子评论排序(是否需要最新)
        :param top_comment_id: 回答的二级评论置顶
        :return:
        """
        _data = {
            "reply_id": reply.id,
            "content": escape(reply.content),
            "user_id": reply.user_id,  # 用于获取用户信息
            "reply_date": get_timestamp_or_none(reply.create_time),
            'comments': [],
            "favor_amount": reply.like_num,
            'reply_count': 0,  # 子评论数
            'is_liked': False,  # 是否点赞,这个后期再拼接
            "comments_map_ids": []  # 子评论映射列表
        }

        if need_sub_comments:
            _data["comments_map_ids"] = self.get_reply_sub_comments(
                reply_id=reply.id,
                need_newest=need_newest,
                top_comment_id=top_comment_id
            )

        return _data

    def get_reply_data_by_replies_obj(self, replies, viewer_user_id=None, need_sub_comments=False, need_newest=False, top_comment_id=None):
        """
        通过评论obj对象获取评论数据
        :param replies:
        :param viewer_user_id: 当前作者用户id
        :param need_sub_comments: 是否取子评论
        :param need_newest: 子评论排序(是否需要最新)
        :param top_comment_id: 回答的二级评论置顶
        :return:
        """
        result = {}
        if not replies:
            return result

        valid_user_ids = set()
        sub_comment_map_ids = []
        for reply in replies:
            valid_user_ids.add(reply.user_id)
            _data = self.get_reply_base_data(
                reply,
                need_sub_comments=need_sub_comments,
                need_newest=need_newest,
                top_comment_id=top_comment_id
            )
            sub_comment_map_ids.extend(_data.get("comments_map_ids", []))
            result[reply.id] = _data

        _reply_ids = list(result.keys())
        # 评论的用户信息
        reply_user_infos = UserConvertService.get_user_info_by_user_ids(user_ids=valid_user_ids)
        # 评论的子评论信息
        sub_comments_data = self.get_comments_data(sub_comment_map_ids)
        # 评论的子评论数
        reply_comment_nums = self.get_reply_comment_nums_by_ids(_reply_ids)
        # 评论的点赞状态
        reply_liked_status = self.reply_is_voted_by_ids(_reply_ids, viewer_user_id=viewer_user_id)
        # 评论的图片
        reply_images_data = self.get_reply_images_by_reply_ids(_reply_ids)

        for reply_id, reply_data in result.items():
            reply_data.update({
                "reply_user_info": reply_user_infos.get(reply_data.get("user_id", 0), {}),
                "reply_count": reply_comment_nums.get(reply_id, 0),
                "comments": list(filter(None, (
                    sub_comments_data.get(map_id) for map_id in reply_data.pop("comments_map_ids", [])))),
                "is_liked": reply_liked_status.get(reply_id, False),
                "images": reply_images_data.get(reply_id, []),
            })

        return result


answer_reply_manager = AnswerReplyManager()