topicranking.py 4.71 KB
# coding=utf-8

from __future__ import print_function, absolute_import, unicode_literals

import datetime
from django.db import models
from django.db.models import Count
from django.utils import timezone
from .topic import Problem
from .topicreply import TopicReply
from .topicvote import TopicVote


class TopicRankingScore(models.Model):
    """
    帖子用于排序的各种分数
    """
    REPLY_COEF = 5.0
    REPLY_BASE = 0.95
    VOTE_COEF = 1.0
    VOTE_BASE = 0.1

    SPLIT_HOUR = 4

    class Meta:
        db_table = 'api_topicrankingscore'
        app_label = 'talos'

    topic = models.OneToOneField(Problem, related_name='ranking_score')
    reply_score = models.FloatField(verbose_name=u'历史(今天之前)回复热度分', default=0.0)  # 未加权
    vote_score = models.FloatField(verbose_name=u'历史(今天之前)点赞分', default=0.0)  # 未加权
    last_modified = models.DateTimeField(auto_now=True, verbose_name=u'最后更新时间')

    @classmethod
    def get_daily_pop(cls, topic_ids, day_offset=0):
        """
        如果时间的小时数大等于SPLIT_HOUR,则只计算所在日的个数
        否则将计算所在日和前一日的个数
        :param topic:
        :param day_offset: 往前回溯几日,0为当天,1为昨天
        :return:
        """
        t = timezone.now()-datetime.timedelta(days=day_offset)
        end_time = (t+datetime.timedelta(days=1)).replace(hour=0, minute=0, second=0)
        if t.hour < cls.SPLIT_HOUR:
            start_time = (t-datetime.timedelta(days=1)).replace(hour=0, minute=0, second=0)
        else:
            start_time = t.replace(hour=0, minute=0, second=0)

        reply_count = TopicReply.objects.filter(
            problem_id__in=topic_ids,
            reply_date__gte=start_time, reply_date__lt=end_time
        ).values('problem_id').annotate(count=Count('*'))
        vote_count = TopicVote.objects.filter(
            topic_id__in=topic_ids,
            vote_time__gte=start_time, vote_time__lt=end_time
        ).values('topic_id').annotate(count=Count('*'))

        results = {topic_id:{'reply_count':0, 'vote_count':0} for topic_id in topic_ids}
        for rcnt in reply_count:
            results[rcnt['problem_id']]['reply_count'] = rcnt['count']
        for vcnt in vote_count:
            results[vcnt['topic_id']]['vote_count'] = vcnt['count']
        return results

    @classmethod
    def update_pop(cls, topic_ids):
        """
        (定时任务)(批量)更新帖子历史分数
        :param topic:
        :return:
        """
        ranking_scores = []
        for topic_id in topic_ids:
            ranking_score, created = cls.objects.get_or_create(topic_id=topic_id)
            ranking_scores.append(ranking_score)
        daily_pop = cls.get_daily_pop(topic_ids=topic_ids, day_offset=1)  # 获取昨日的分数
        for ranking_score in ranking_scores:
            topic_id = ranking_score.topic_id
            ranking_score.reply_score = (ranking_score.reply_score+daily_pop[topic_id]['reply_count'])*cls.REPLY_BASE
            ranking_score.vote_score = (ranking_score.vote_score+daily_pop[topic_id]['vote_count'])*cls.VOTE_BASE
            ranking_score.save()

    @classmethod
    def get_pop_score(cls, topic):
        """DEPRECATED

        获取帖子当前的热度分
        :param topic:
        :return:
        """
        assert isinstance(topic, Problem)
        daily_pop = cls.get_daily_pop(topic_ids=[topic.id])
        daily_pop = daily_pop[topic.id]
        try:
            ranking_score = cls.objects.get(topic=topic)
            old_reply_score = ranking_score.reply_score
            old_vote_score = ranking_score.vote_score
        except cls.DoesNotExist:
            old_reply_score = 0.0
            old_vote_score = 0.0

        return cls.REPLY_COEF*(old_reply_score+daily_pop['reply_count']) + \
               cls.VOTE_COEF*(old_vote_score+daily_pop['vote_count'])


    @classmethod
    def get_pop_scores(cls, topic_ids):
        daily_pops = cls.get_daily_pop(topic_ids=topic_ids)
        ranking_scores = cls.objects.filter(topic_id__in=topic_ids)
        ranking_scores = {s.topic_id: s for s in ranking_scores}

        res = {}
        for topic_id in topic_ids:
            reply_count = daily_pops[topic_id]['reply_count']
            vote_count = daily_pops[topic_id]['vote_count']
            if topic_id in ranking_scores:
                old_reply_score = ranking_scores[topic_id].reply_score
                old_vote_score = ranking_scores[topic_id].vote_score
            else:
                old_reply_score = 0.0
                old_vote_score = 0.0
            res[topic_id] = cls.REPLY_COEF*(old_reply_score+reply_count) + \
                            cls.VOTE_COEF*(old_vote_score+vote_count)
        return res