# -*- coding: utf-8 -*- from __future__ import unicode_literals, absolute_import, print_function import re import logging import traceback import datetime import time from utils.pic import PictureTools import redis, json from cached_property import cached_property from collections import defaultdict from django.db import models from django.conf import settings from django.utils import timezone from django.utils.html import escape from data_sync.utils import to_epoch, tzlc from gm_serializer import fields from gm_types.gaia import USER_TYPE, VOTEOBJECT, DIARY_CONTENT_LEVEL, TAG_TYPE from gm_types.mimas import ( SPAM_LABEL, GRABBING_PLATFORM, QUESTION_TYPE, SEND_ANSWER_STATUS, QA_CONTENT_TYPE, MEDIA_IMAGE_URL_SOURCE, IMAGE_TYPE, QUESTION_AUDIT_STATUS, ) from gm_types.mimas.qa import CONTENT_CLASS, VIDEO_SOURCE_TYPE from gm_types.push import AUTOMATED_PUSH from gm_upload import ImgUrlField, IMG_TYPE from talos.services import UserConvertService from talos.services.tag_v3 import TagV3Service from utils.rpc import RPCMixin from qa.cache import ViewRecord, answer_cache from qa.utils import const_strings from qa.utils.image import get_w_path, get_thumb_path, get_half_path from qa.utils.time import get_humanize_datetime, get_timestamp_or_none, get_timestamp from qa.utils.user import get_auth_type_by_userid, get_user_level from qa.utils.get_video_cover import get_video_cover_url from utils.exceptions import Impossible from utils.common import convert_image from utils.user import get_user_gm_url from utils.protocol import gm_protocol from talos.services.tag import TagService # from talos.models.tractate.tractate import StrategyContentExposureIndex doris_redis_client = redis.StrictRedis.from_url(settings.REDIS_URL) class UserManager(RPCMixin): def __call__(self, pk_list): """ :param pk_list: :return: """ return self.call_rpc('api/user/get_fundamental_info_by_user_ids', user_ids=pk_list) class TagManager(RPCMixin): def __call__(self, pk_list): return self.call_rpc('api/tag/info_by_ids', tag_ids=pk_list) class ApiAnswerScore(models.Model): class Meta: db_table = 'api_answer_score' answer_id = models.IntegerField(unique=True) new_score = models.FloatField(verbose_name=u'新的answer_score', blank=True, default=0) class ApiAnswerScoreV2(models.Model): class Meta: db_table = 'api_answer_score_v2' answer_id = models.IntegerField(unique=True) new_score = models.FloatField(verbose_name=u'新的answer_score', blank=True, default=0) class Question(models.Model): class Meta: verbose_name = u'问答' db_table = 'api_question' app_label = 'qa' user = fields.MagicField(type=int, manager=UserManager, ttl=60 * 5, db_column="user_id") title = models.CharField(max_length=128, null=False, verbose_name=u'问题') content = models.TextField(null=True, verbose_name=u'描述') cover_url = ImgUrlField(img_type=IMG_TYPE.NOWATERMARK, max_length=300, verbose_name=u'原图片地址', null=True, blank=True, default=None) is_online = models.BooleanField(verbose_name='是否在线', default=True) is_recommend = models.BooleanField(verbose_name='是否推荐', default=False) recommend_xiaochengxu = models.BooleanField(verbose_name='吐槽小程序,是否推荐', default=False) like_num = models.IntegerField(verbose_name='点赞数量', default=0) create_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now) update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True) # is_spam字段已弃用,请使用spam_label is_spam = models.BooleanField(verbose_name=u'是否为垃圾回答', default=False) spam_label = models.SmallIntegerField(verbose_name=u'spam标签', default=SPAM_LABEL.NORMAL, choices=SPAM_LABEL) question_type = models.IntegerField(verbose_name=u'问题类型', max_length=3, default=QUESTION_TYPE.TRADE, choices=QUESTION_TYPE) # from topic problem_id = models.IntegerField(verbose_name='帖子id', null=True, default=None, unique=True) city_id = models.CharField(max_length=40, null=True, verbose_name=u'用户对应的城市id') is_invite_doctor = models.BooleanField(verbose_name='是否邀请医生回答', default=False) platform = models.CharField(u'抓取平台信息', max_length=2, choices=GRABBING_PLATFORM, default=None, null=True) # 7675后,平台支持富文本展示content格式数据不同 新增类型 platform_id = models.CharField(verbose_name='抓取平台ID', max_length=40, default=None, db_index=True) platform_tag = models.CharField(verbose_name='抓取平台标签中文', max_length=100, default=None) # v 7.7.10 新增内容类型字段 枚举值 用于数据区分及召回 content_type = models.CharField(u'内容类型', max_length=2, choices=QA_CONTENT_TYPE, default=QA_CONTENT_TYPE.ORDINARY) audit_status = models.CharField(verbose_name=u'审核状态', max_length=1, choices=QUESTION_AUDIT_STATUS, default=QUESTION_AUDIT_STATUS.UNAUDITED) def user_can_view(self, user_id): if user_id == self.user_id: if self.audit_status == QUESTION_AUDIT_STATUS.AUDITED and not self.is_online: return False elif not self.is_online: return False return True @cached_property def tags(self): """ :return: """ return QuestionTag.objects.filter(question=self) @staticmethod def _get_view_amount_of_id(question_id): return ViewRecord(const_strings.QUESTION_VIEW)[question_id] or 0 @staticmethod def set_view_amount_of_id(question_id, num): ViewRecord(const_strings.QUESTION_VIEW)[question_id] = str(num) @staticmethod def _get_vote_amount_of_id(question_id): return ViewRecord(const_strings.QUESTION_VIEW)[question_id] or 0 @property def vote_amount(self): """7720修改 话题点赞数*7""" return int(self._get_vote_amount_of_id(self.id)) * 7 @cached_property def view_amount(self): """ v 7.6.40 浏览量计算规则调整:问题本身浏览量 + 该问题下所有回答浏览量 v 7.7.20 浏览量*13 :return: """ num1 = int(self._get_view_amount_of_id(self.id)) # 问题本身的浏览量 answer_ids = self.answers.filter(is_online=True).values_list("id", flat=True) num2 = sum(map(int, filter(None, ViewRecord(const_strings.ANSWER_VIEW).view_hmget(answer_ids)))) # num2 = sum([int(answer.view_amount) for answer in self.answers.filter(is_online=True)]) # 所有回答的浏览量 return (num1 + num2) * 13 @cached_property def answer_num(self): return self.answers.filter(is_online=True).count() @cached_property def last_answer_time(self): last_answer = self.answers.filter(is_online=True).order_by("-id").first() if last_answer: return get_timestamp_or_none(last_answer.create_time) return None @property def comment_num(self): answers_ids = list(self.answers.filter(is_online=True).values_list('id', flat=True)) if not answers_ids: return 0 answers_reply_num = AnswerReply.objects.using(settings.SLAVE_DB_NAME).filter(is_online=True, answer_id__in=answers_ids).count() # 所有回答的评论数 return len(answers_ids) + answers_reply_num @property def recommend_answer(self): answer = self.answers.filter(is_recommend=True, is_online=True) if answer: return answer.first() return None @property def fisrt_answer(self): answer = self.answers.filter(is_recommend=True, is_online=True) if answer: return answer.first() return self.answers.filter(is_online=True).order_by('-like_num').first() def data_for_list(self, user=None): if self.cover_url: cover_url = get_w_path(self.cover_url) else: cover_url = '' if self.content: video_urls = re.findall('(' + settings.VIDEO_HOST + '.*?)\"', self.content) video_cover_list = get_video_cover_url(source_id=self.id, source_type=VIDEO_SOURCE_TYPE.QUESTION, video_urls=video_urls) else: video_cover_list = None data = { 'user_id': self.user.id, 'user_name': self.user.nickname, 'user_portrait': self.user.portrait, 'membership_level': self.user.membership_level, 'title': self.title, 'content': escape(self.content) if self.content else '', 'row_content': self.content or '', 'image': cover_url, 'answer_num': str(self.comment_num), 'timestamp': int(self.create_time.strftime("%s")), 'question_id': str(self.id), 'time': get_humanize_datetime(self.create_time), 'user_level': get_user_level(self.user), 'view_num': self.view_amount, 'tags': [tag.tag for tag in self.tags if tag.tag], 'content_images': self.content_images, 'platform': self.platform, 'question_type': self.question_type, 'video_cover_list': video_cover_list, } return data def data_for_discovery(self): """发现页""" if self.cover_url: cover_url = get_w_path(self.cover_url) else: cover_url = '' update_time = self.create_time answers = self.answers.filter(is_online=True).order_by("-create_time").values("id", "create_time") if answers: update_time = max(answers[0]["create_time"], update_time) ids = [i["id"] for i in answers] reply = AnswerReply.objects.filter(is_online=True, answer__in=ids). \ order_by("-create_time").first() if reply: update_time = max(reply.create_time, update_time) data = { "title": self.title, "question_id": self.id, 'title': self.title, 'content': escape(self.content) if self.content else '', 'row_content': self.content or '', 'image': cover_url, 'question_id': str(self.id), 'desc': get_humanize_datetime(update_time), 'tags': [tag.tag for tag in self.tags if tag.tag], 'content_images': self.content_images, 'platform': self.platform, } return data def data_for_doctor(self): data = { 'user_id': self.user_id, 'user_name': self.user.nickname, 'user_portrait': self.user.portrait, 'membership_level': self.user.membership_level, 'title': self.title, 'answer_num': str(self.answer_num), 'question_id': str(self.id), 'time': get_humanize_datetime(self.create_time), } return data def data_for_my_list(self, need_view_amount=True): data = { 'title': self.title, 'content': escape(self.content), 'answer_num': str(self.answer_num), 'vote_num': self.vote_amount, 'question_id': str(self.id), 'time': get_humanize_datetime(self.create_time), 'create_time': self.create_time.strftime('%y-%m-%d'), 'platform': self.platform } if need_view_amount: data["view_num"] = self.view_amount return data def data_for_es(self): answer = self.fisrt_answer if answer: return answer.data_for_es() return { 'title': self.title, 'content': '', 'vote_num': 0, 'comment_num': self.answer_num, 'question_id': self.id, 'answer_id': '' } @property def content_images(self): _m = [] images = self.images.filter( image_url_source=MEDIA_IMAGE_URL_SOURCE.CREATE ).all() for image in images: if image.image_url: _m.append(image.image_url) return _m def get_tags_v3_info(self): """ 问题对应的tag_v3信息 :return: [{'id': 11, 'tag_type':'11', 'name': '标签名'}] """ tag_v3_id_list = QuestionTagV3.objects.filter(question_id=self.id).values_list('tag_v3_id', flat=True) tags_v3 = [ { 'id': tag.id, 'tag_type': str(tag.tag_type), 'name': tag.name } for tag in TagV3Service.get_tags_by_tag_v3_ids(tag_v3_id_list).values() ] return tags_v3 class QuestionImage(models.Model): class Meta: verbose_name = u'问答图片' db_table = 'api_question_image' app_label = 'qa' question = models.ForeignKey(Question, related_name='images') image_url = ImgUrlField(img_type=IMG_TYPE.TOPICIMAGE, max_length=300, verbose_name=u'图片地址') width = models.IntegerField(verbose_name="图片宽度", default=0) height = models.IntegerField(verbose_name="图片高度", default=0) image_url_source = models.CharField(verbose_name="图片地址来源(创建、富文本)", max_length=3, choices=MEDIA_IMAGE_URL_SOURCE) image_type = models.IntegerField(verbose_name="图片类型", default=IMAGE_TYPE.OTHER) image_webp = models.CharField(max_length=128, default="", verbose_name=u'webp格式的图片') create_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now) update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True) def data(self): return { 'image': get_w_path(self.image_url), } @property def image_info_data(self): return { "image": self.image_url, "width": self.width, "height": self.height, "image_webp": self.image_webp, } class QuestionTag(models.Model): class Meta: db_table = 'api_questiontag' app_label = 'qa' question = models.ForeignKey(Question, related_name='qtags') tag = fields.MagicField(type=int, manager=TagManager, ttl=60 * 60 * 24, db_column="tag_id") def get_name_list(self, tag_list): logging.info("get tag_list:%s" % tag_list) manager = TagManager() name = list() ma = manager.__call__(tag_list) for i in ma: logging.info("get manage_name:%s" % i['name']) name.append(i['name']) return name def get_project_tags(self, tag_list): try: tag_list = TagService._get_by_ids_from_cache_type(list(tag_list)) return [t.name for t in tag_list if t.tag_type in (TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM, TAG_TYPE.ITEM_WIKI)] except: return [] class QuestionTagV3(models.Model): class Meta: db_table = 'api_question_tag_v3' app_label = "qa" question_id = models.IntegerField(verbose_name="问题id", db_index=True) tag_v3_id = models.IntegerField(verbose_name="标签V3", db_index=True) class QuestionTagV4(models.Model): class Meta: db_table = 'api_question_tag_v4' app_label = "qa" question_id = models.IntegerField(verbose_name="问题id", db_index=True) tag_v4_id = models.IntegerField(verbose_name="标签V4", db_index=True) class QuestionVote(models.Model): class Meta: app_label = 'qa' db_table = 'api_question_vote' verbose_name = u'问答点赞' user = fields.MagicField(type=int, manager=UserManager, ttl=60 * 5, db_column="user_id") question = models.ForeignKey(Question, related_name="votes") unread = models.BooleanField(default=True) is_fake = models.BooleanField(default=False, verbose_name=u"是否机器人点赞") create_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now) update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True) class Answer(models.Model): class Meta: app_label = 'qa' db_table = 'api_answer' verbose_name = u'问答回复' user = fields.MagicField(type=int, manager=UserManager, ttl=60 * 5, db_column="user_id") content = models.TextField(verbose_name='回答', null=False) cover_url = ImgUrlField(img_type=IMG_TYPE.NOWATERMARK, max_length=300, verbose_name=u'原图片地址', null=True, blank=True, default=None) question = models.ForeignKey(Question, verbose_name=u"话题回复", related_name='answers') is_online = models.BooleanField(verbose_name='是否在线', default=True) is_recommend = models.BooleanField(verbose_name='是否推荐', default=False) like_num = models.IntegerField(verbose_name='点赞数量', default=0) create_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now) update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True) # is_spam字段已弃用,请使用spam_label is_spam = models.BooleanField(verbose_name=u'是否为垃圾回答', default=False) spam_label = models.SmallIntegerField(default=SPAM_LABEL.NORMAL, choices=SPAM_LABEL, verbose_name=u"spam标签") level = models.IntegerField('分级', default=0) rank = models.IntegerField('展示序列', default=999) platform = models.CharField(u'抓取平台信息', max_length=2, choices=GRABBING_PLATFORM, default=None, null=True) platform_id = models.CharField(verbose_name='抓取平台ID', max_length=40, default=None, db_index=True) platform_tag = models.CharField(verbose_name='抓取平台标签中文', max_length=100, default=None) # v 7.6.40新加 用于消息 - 回答 计数使用! questioner_read = models.BooleanField(verbose_name='提问者是否已读', default=False) # v 7.6.70 新增 用户记录:当前回答的用户关联的医生id doctor_id = models.CharField(max_length=100, verbose_name="回答用户关联的医生id", default=None, null=True) doctor_title = models.CharField(max_length=2, verbose_name="回答用户关联的医生职称", default=None, null=True) # v 7.7.10 新增内容类型字段 枚举值 用于数据区分及召回 content_type = models.CharField(u'内容类型', max_length=2, choices=QA_CONTENT_TYPE, default=QA_CONTENT_TYPE.ORDINARY) # from topicreply topicreply_id = models.IntegerField(verbose_name='帖子id', null=True, default=None, unique=True) low_quality = models.IntegerField(default=0, verbose_name=u'低质量反馈数量', blank=True, null=False) low_quality_deal = models.BooleanField(default=False, verbose_name=u'低质量反馈是否处理', blank=True) @cached_property def tags(self): """ :return: """ return AnswerTag.objects.filter(answer=self) @staticmethod def _get_view_amount_of_id(answer_id): return ViewRecord(const_strings.ANSWER_VIEW)[answer_id] or 0 @property def view_amount(self): return int(self._get_view_amount_of_id(self.id)) @staticmethod def set_view_amount_of_id(answer_id, num): ViewRecord(const_strings.ANSWER_VIEW)[answer_id] = str(num) @cached_property def comment_num(self): """ v 7.6.95变更,评论数为:1级评论 + 子评论 :return: """ return self.replys.filter(is_online=True).count() @cached_property def first_reply_num(self): return self.replys.filter(is_online=True, first_reply=None).count() def get_cover_image(self): if self.cover_url: return [convert_image(self.cover_url, watermark=True)] else: return [] def get_all_images(self): data = [] for image in self.images.filter(image_url_source=MEDIA_IMAGE_URL_SOURCE.CREATE).order_by('id'): data.append(image.data()) return data def get_images(self): """获取所有的图片""" data = [] for image in self.images.all().order_by('id'): data.append(image.data()) return data def data_for_es(self, need_view_amount=True): data = { 'title': self.question.title, 'content': escape(self.content), 'vote_num': self.like_num, 'comment_num': self.comment_num, 'question_id': self.question_id, 'answer_id': self.id, } if need_view_amount: data['view_num'] = self.view_amount return data def data_for_list(self, user=None): is_vote = False if user: is_vote = AnswerVote.objects.filter(user=user, answer=self).exists() if self.content: video_urls = re.findall('(' + settings.VIDEO_HOST + '.*?)\"', self.content) video_cover_list = get_video_cover_url(source_id=self.id, source_type=VIDEO_SOURCE_TYPE.ANSWER, video_urls=video_urls) else: video_cover_list = None data = { 'user_id': self.user_id, 'title': self.question.title, 'content': escape(self.content) if self.content else "", 'raw_content': self.content or "", 'time': get_humanize_datetime(self.create_time), 'user_name': self.user.nickname, 'user_portrait': self.user.portrait, 'membership_level': self.user.membership_level, 'vote_num': str(self.like_num), 'comment_num': str(self.comment_num), 'answer_id': str(self.id), 'question_id': str(self.question_id), 'images': self.get_cover_image(), 'content_images': self.content_images, 'is_voted': is_vote, 'user_level': get_user_level(self.user), 'view_num': self.view_amount, 'platform': self.platform, 'question_type': self.question.question_type, 'video_cover_list': video_cover_list, } return data @property def cover_is_dynamic(self): # video_cover_list -> question_videos -> answer.cover_url -> header_images -> # intact_answer_images -> _question_intact_question_images from qa.manager.answer_manager import AnswerManager from qa.manager.qa_media_manager import answer_media, question_media answer_text_dic = {self.id: self.content} answer_videos_dic = answer_media.get_qa_videos(answer_text_dic, source_type=VIDEO_SOURCE_TYPE.ANSWER) if answer_videos_dic.get(self.id): return True question_text_dic = {self.question_id: self.question.content} question_videos_dic = question_media.get_qa_videos(question_text_dic, source_type=VIDEO_SOURCE_TYPE.QUESTION) if question_videos_dic.get(self.question_id): return True if PictureTools.is_dynamic(self.cover_url): return True answer_header_image_dict = AnswerManager.get_header_imgs_by_ids([self.id]) if answer_header_image_dict: imgs = answer_header_image_dict.get(self.id, []) or [] if imgs and (imgs[0].get('image_webp') or PictureTools.is_dynamic(imgs[0].get('image_url') or imgs[0].get('image'))): return True answer_images_dic = answer_media.get_qa_images(answer_text_dic, image_url_sources=[VIDEO_SOURCE_TYPE.ANSWER]) if answer_images_dic: imgs = answer_images_dic.get(self.id, []) or [] if imgs and (imgs[0].get('image_webp') or PictureTools.is_dynamic(imgs[0].get('image_url') or imgs[0].get('image'))): return True question_images_dic = question_media.get_qa_images(question_text_dic, image_url_sources=[VIDEO_SOURCE_TYPE.QUESTION]) if question_images_dic: imgs = question_images_dic.get(self.question_id, []) or [] if imgs and (imgs[0].get('image_webp') or PictureTools.is_dynamic(imgs[0].get('image_url') or imgs[0].get('image'))): return True return False @staticmethod def has_video(answer): if answer.content: video_urls = re.findall('(' + settings.VIDEO_HOST + '.*?)\"', answer.content) if video_urls: return True if answer.question.content: video_urls = re.findall('(' + settings.VIDEO_HOST + '.*?)\"', answer.question.content) if video_urls: return True return False def get_base_info(self): if self.content: video_urls = re.findall('(' + settings.VIDEO_HOST + '.*?)\"', self.content) video_cover_list = get_video_cover_url(source_id=self.id, source_type=VIDEO_SOURCE_TYPE.ANSWER, video_urls=video_urls) else: video_cover_list = None if self.question.content: video_urls = re.findall('(' + settings.VIDEO_HOST + '.*?)\"', self.question.content) question_video_cover_list = get_video_cover_url(source_id=self.question.id, source_type=VIDEO_SOURCE_TYPE.QUESTION, video_urls=video_urls) else: question_video_cover_list = None data = { 'user_id': self.user_id, 'title': self.question.title, 'content': escape(self.content) if self.content else "", 'raw_content': self.content or "", 'time': get_humanize_datetime(self.create_time), 'create_time': get_timestamp_or_none(self.create_time), 'user_name': "", 'user_portrait': "", 'membership_level': "", 'user_level': get_user_level(None), 'answer_id': str(self.id), 'view_num': self.view_amount, 'question_id': str(self.question_id), 'content_images': self.content_images, 'video_cover_list': video_cover_list, 'cover_url': self.cover_url, 'platform': self.platform, 'question_type': self.question.question_type, 'question_content_images': self.question.content_images, 'question_answer_num': self.question.answer_num, 'question_videos': question_video_cover_list, 'question_content': self.question.content, } return data def _get_cache_key(self): return 'qa:answer-v1m:%s' % self.id def del_cache(self): k = self._get_cache_key() answer_cache.delete(k) def _get_or_init_cached_data(self): k = self._get_cache_key() _cached = answer_cache.get(k) if _cached: return json.loads(_cached) answer_data = self.get_base_info() answer_cache.setex(k, 10 * 60, json.dumps(answer_data)) return answer_data def data_for_qa(self, user=None): is_vote = False if user: is_vote = AnswerVote.objects.filter(user=user, answer=self).exists() data = self._get_or_init_cached_data() data.update({ 'vote_num': self.like_num, 'comment_num': self.comment_num, 'question_answer_num': self.question.answer_num, 'last_answer_time': self.question.last_answer_time, 'is_voted': is_vote, }) return data def data_for_detail(self, user): is_vote = False if user: is_vote = AnswerVote.objects.filter(user=user, answer=self).exists() if self.user_id == settings.SUOZHANG_UID or self.platform in GRABBING_PLATFORM: content = self.content else: content = escape(self.content) return { 'id': self.id, 'post_date': self.create_time.strftime('%y-%m-%d'), 'content': content, 'vote_count': self.like_num, 'comment_count': self.comment_num, 'image': self.get_all_images(), 'is_online': self.is_online, 'is_voted': is_vote, 'tags': [tag.tag for tag in self.question.tags if tag.tag], # 取其question的tag 'platform': self.platform, # 平台信息 'first_reply_num': self.first_reply_num, # 回答的一级评论数,用于查看更多一级评论! 'create_time': self.create_time.timestamp(), } def get_videos(self): if self.content: video_urls = re.findall('(' + settings.VIDEO_HOST + '.*?)\"', self.content) video_cover_list = get_video_cover_url(source_id=self.id, source_type=VIDEO_SOURCE_TYPE.ANSWER, video_urls=video_urls) else: video_cover_list = [] return video_cover_list def data_for_author(self, user=None): user_info = UserConvertService.get_user_info_by_user_id(self.user_id) user_info.update({ 'user_type': 0, "user_portrait": self.user.portrait, 'is_following': RPCMixin.call_rpc('api/user/is_following', cuid=user.id, uid=self.user.id) if user else False, 'user_level': get_user_level(self.user), "college_id": self.user.get("college_id", 0), "college_name": self.user.get("college_name", ""), }) return user_info @property def content_images(self): _m = [] images = self.images.filter( image_url_source=MEDIA_IMAGE_URL_SOURCE.CREATE ).all() for image in images: if image.image_url: _m.append(image.image_url) return _m @property def get_good_click(self): try: from talos.models.soft_article.soft_article import PageGoodClick results = list( PageGoodClick.objects.filter(business_id=self.id, page_name="answer_detail") .order_by('-create_date').values_list("avg_new_gc", flat=True)) good_click_score = results and results[0] or 0 return good_click_score except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return 0 @property def con_good_click(self): try: from talos.models.soft_article.soft_article import GoodClickCom today = datetime.datetime.now() - datetime.timedelta(days=1) lastday = str(datetime.datetime(today.year, today.month, today.day)) lastday = lastday.split(" ") date_change = lastday[0].replace("-", "") results = list( GoodClickCom.objects.using("doris").filter(business_id=str(self.id), page_name="answer_detail", create_date=str(date_change)).values_list( "goodclick_rate_30", flat=True)) good_click_score = results and results[0] or 0 return good_click_score except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return 0 def data_for_received(self): data = { "user": { 'doctor_id': '', 'user_id': self.user_id, }, "answer": { "id": str(self.id), "content": self.content, "create_time": self.create_time.strftime("%Y-%m-%d %H:%M:%S"), "is_new": not self.questioner_read, "user_id": self.user_id, "username": self.user.nickname, "portrait": self.user.portrait, }, "question": { "nickname": self.question.user.nickname, "portrait": self.question.user.portrait, "title": self.question.title, "is_deleted": not self.question.is_online, }, } return data def _content_score(self): if self.level in [CONTENT_CLASS.EXCELLENT, CONTENT_CLASS.OUTSTANDING]: return 100 elif self.level == CONTENT_CLASS.FINE: return 70 elif self.level == CONTENT_CLASS.GENERAL: return 10 elif self.level == CONTENT_CLASS.BAD: return 5 else: return 0 def _social_score(self): pass def _comment_score(self): comment_num = self.comment_num if comment_num == 0: return 0 elif 1 <= comment_num <= 10: return 20 elif 11 <= comment_num <= 20: return 40 elif 21 <= comment_num <= 40: return 70 elif 41 <= comment_num: return 100 else: raise Impossible def _like_score(self): like_num = self.like_num if like_num == 0: return 0 elif 1 <= like_num <= 10: return 20 elif 11 <= like_num <= 20: return 40 elif 21 <= like_num <= 30: return 70 elif 30 <= like_num: return 100 else: raise Impossible def _time_score(self): created_date = self.create_time.date() delta = created_date - settings.FOUNDING_DAY return delta.days def smart_rank(self): content_score = self._content_score() social_score = settings.SOCIAL_REPLY_WEIGHT * self._comment_score() + \ settings.SOCIAL_LIKE_WEIGHT * self._like_score() rank = settings.CONTENT_WEIGHT * content_score + \ settings.SOCIAL_WEIGHT * social_score + \ settings.TIME_WEIGHT * self._time_score() return rank def smart_rank_v2(self): try: result = ApiAnswerScore.objects.using(settings.DORIS_DB_NAME).filter(answer_id=self.id).first() return result.new_score if result else 0.0 except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return 0.0 def smart_rank_v3(self): try: result = ApiAnswerScoreV2.objects.using(settings.DORIS_DB_NAME).filter(answer_id=self.id).first() return result.new_score if result else 0.0 except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return 0.0 def get_hot_score_answer(self): try: now = datetime.datetime.now() three_day = datetime.datetime.now() - datetime.timedelta(days=60) yesterday_begin_time = "%s-%s-%s 00:00:00" % (three_day.year, three_day.month, three_day.day) # 点赞数+3评论数+5收藏数 # 获得点赞数 vote_num = AnswerVote.objects.filter(answer_id=self.id, create_time__gte=yesterday_begin_time, create_time__lte=now).count() # 获得评论数 reply_num = AnswerReply.objects.filter(answer_id=self.id, create_time__gte=yesterday_begin_time, create_time__lte=now, is_online=True).count() return vote_num + 3 * int(reply_num) + 0 except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return [] def get_last_any_reply_time(self): try: result = AnswerReply.objects.filter(answer_id=self.id).values_list("create_time", flat=True).order_by( "-create_time").first() return result except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return '' def get_has_picture(self): # 是否有图片 try: answer_content = self.content re_pattern = r'.*[img|IMG|video|VIDEO] src="(.*)" .*' src_result = re.findall(re_pattern, answer_content) if src_result: return True else: return False except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return False def nofake_comment_num(self, answer_id): count = AnswerReply.objects.filter(answer_id=answer_id, is_fake=False, is_online=True).values_list("user", flat=True).distinct().count() return count def nofake_vote_number(self, answer_id): count = AnswerVote.objects.filter(answer_id=answer_id, is_fake=False).values_list("user", flat=True).distinct().count() return count # 获取回答贴最新互动时间 def get_answer_latest_interaction_time(self, is_online, content_level, create_time): try: update_time_value = -1 if is_online: update_time_value = int(time.mktime(create_time.timetuple())) if create_time else -1 # 最新点赞时间 vote_result = AnswerVote.objects.filter(answer_id=self.id).values_list("update_time", flat=True).order_by( "-update_time").first() vote_time_value = int(time.mktime(tzlc(vote_result).timetuple())) if vote_result else -1 if vote_time_value > update_time_value: update_time_value = vote_time_value # 最新回复时间 reply_result = AnswerReply.objects.filter(answer_id=self.id).values_list("update_time", flat=True).order_by( "-update_time").first() reply_time_value = int(time.mktime(tzlc(reply_result).timetuple())) if reply_result else -1 if reply_time_value > update_time_value: update_time_value = reply_time_value # 小组创建时间 answer_group_redis_name = "doris:answer_group_data" redis_tractate_group_data = doris_redis_client.hget(answer_group_redis_name, str(self.id)) answer_group_id_list = json.loads( str(redis_tractate_group_data, encoding="utf-8")) if redis_tractate_group_data else [] if len(answer_group_id_list) > 0: from talos.models.soft_article.soft_article import Group group_update_time_result = Group.objects.using(settings.ZHENGXING_DB).filter( id__in=answer_group_id_list).values_list( "create_time", flat=True).order_by("-create_time").first() group_update_time_value = int( time.mktime(tzlc(group_update_time_result).timetuple())) if group_update_time_result else -1 if group_update_time_value > update_time_value: update_time_value = group_update_time_value + 5 return update_time_value except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return -1 def get_new_smart_rank_score(self, answer_id): try: today = datetime.datetime.now().date() delta = datetime.timedelta(days=1) n_days = today - delta date_change = str(n_days).replace("-", "") score = StrategyAnswerSmrScore.objects.using(settings.DORIS_DB_NAME).filter( answer_id=answer_id).order_by('-create_date').values_list("smart_rank_score", flat=True).first() if score: return score else: return 0 except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return 0 def get_answer_newuser_smr(self, answer_id): score = SrategyAnswerSmrScore.objects.using(settings.DORIS_DB_NAME).filter( answer_id=answer_id).order_by('-create_date').values_list("smart_rank_score", flat=True).first() if score: return score else: return 0 def get_answer_tagv4_names(self, answer_id): try: data = AnswerTagV4.objects.using(settings.HERA_READ_DB).filter(answer_id=answer_id).values_list( "tag_v4_id", flat=True) return data except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return [] def get_search_new_smart_rank_score(self, answer_id): try: score = SearchStrategyAnswerSmrScore.objects.using(settings.DORIS_DB_NAME).filter( answer_id=answer_id).order_by('-create_date').values("smart_rank_score", "new_goodclick", "smart_rank_v2").first() if score: return score else: return {} except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return {} def get_community_answer_smr_score(self, answer_id): try: score = CommunitysStrategyAnswerSmrScore.objects.using(settings.DORIS_DB_NAME).filter( answer_id=answer_id).order_by('-create_date').values("smart_rank_score").first() if score: return score else: return {} except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return {} def get_community_answer_hot_score(self, answer_id): try: score = CommunitysStrategyAnswerHotScore.objects.using(settings.DORIS_DB_NAME).filter( answer_id=answer_id).order_by('-create_date').values("hot_score").first() if score: return score else: return {} except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return {} def get_answer_show_by_index(self, answer_id): try: from talos.models.tractate.tractate import StrategyContentExposureIndex today = datetime.datetime.now().date() delta = datetime.timedelta(days=2) n_days = today - delta data = StrategyContentExposureIndex.objects.using(settings.DORIS_DB_NAME).filter( create_day=n_days, card_id=answer_id, card_content_type="answer").first() if data: if data.ctr >= 0.05 and data.preciseexposure_num >= 50 and data.avg_page_stay >= 20: return 0 # 0是正常展示 1不展示 return 1 else: return 1 except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return 1 def get_answer_update_time_stratific(self, create_time): try: now = datetime.datetime.now() d2 = datetime.datetime.strptime(now.strftime('%Y-%m-%d %H:%M:%S'), '%Y-%m-%d %H:%M:%S') delta_30 = datetime.timedelta(days=30) delta_90 = datetime.timedelta(days=90) delta_365 = datetime.timedelta(days=365) if d2: if d2 - delta_30 <= create_time: return 30 elif d2 - delta_90 <= create_time: return 90 elif d2 - delta_365 <= create_time: return 365 else: return 1000 else: return 1000 except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return 1000 def get_answer_tag(self, tag_list): try: association_tags_id_list = list() tag_list = TagService._get_by_ids_from_cache_type(tag_list) for item in tag_list: association_tags_id_list.append( {"id": item.id, "tag_name": item.name, "recommend_type": item.recommend_type, "tag_type": item.tag_type}) return association_tags_id_list except: logging.error("catch exception,err_msg:%s" % traceback.format_exc()) return [] class SendAnswer(models.Model): class Meta: app_label = 'qa' db_table = 'api_send_answer' verbose_name = u'定时发送回答' user_id = models.CharField(max_length=32, verbose_name='用户ID') content = models.TextField(verbose_name='回答内容', null=False) cover_url = ImgUrlField(img_type=IMG_TYPE.NOWATERMARK, max_length=300, verbose_name=u'运营配位图', null=True, blank=True, default=None) question = models.ForeignKey(Question, verbose_name=u"问题") create_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now) push_time = models.DateTimeField(verbose_name=u'定时发送时间') update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True) # is_spam字段已弃用,请使用spam_label level = models.IntegerField('分级', default=0) rank = models.IntegerField('展示序列', default=999) status = models.SmallIntegerField(default=SEND_ANSWER_STATUS.WAITTING, choices=SEND_ANSWER_STATUS, verbose_name=u"定时发送回答状态") celery_task_id = models.CharField(verbose_name=u'异步任务ID', max_length=64) @property def data(self): return { 'user': self.user_id, 'content': self.content, 'cover_url': self.cover_url, 'level': self.level, 'rank': self.rank, 'question_id': self.question.id, 'platform': GRABBING_PLATFORM.HERA, # 默认为hera后台 } class AnswerImage(models.Model): class Meta: app_label = 'qa' db_table = 'api_answer_image' verbose_name = u'问答回复图片' answer = models.ForeignKey(Answer, related_name='images') image_url = ImgUrlField(img_type=IMG_TYPE.TOPICREPLY, max_length=300, verbose_name=u'图片地址') width = models.IntegerField(verbose_name="图片宽度", default=0) height = models.IntegerField(verbose_name="图片高度", default=0) image_url_source = models.CharField(verbose_name="图片地址来源(创建、富文本)", max_length=3, choices=MEDIA_IMAGE_URL_SOURCE) image_type = models.IntegerField(verbose_name="图片类型", default=IMAGE_TYPE.OTHER) image_webp = models.CharField(max_length=128, default="", verbose_name=u'webp格式的图片') create_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now) update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True) def data(self): return { 'image': get_w_path(self.image_url), } @property def image_info_data(self): return { "image": self.image_url, "width": self.width, "height": self.height, "image_webp": self.image_webp, } class AnswerTag(models.Model): class Meta: db_table = 'api_answer_tag' app_label = 'qa' answer = models.ForeignKey(Answer, related_name='answer_tags') tag = fields.MagicField(type=int, manager=TagManager, ttl=60 * 60 * 24, db_column="tag_id", db_index=True) class AnswerTagV3(models.Model): class Meta: db_table = 'api_answer_tag_v3' app_label = "qa" answer_id = models.IntegerField(verbose_name="回答id", db_index=True) tag_v3_id = models.IntegerField(verbose_name="标签V3", db_index=True) class AnswerTagV4(models.Model): class Meta: db_table = 'api_answer_tag_v4' app_label = "qa" answer_id = models.IntegerField(verbose_name="回答id", db_index=True) tag_v4_id = models.IntegerField(verbose_name="标签V4", db_index=True) class AnswerAttrTagV3(models.Model): class Meta: db_table = 'api_answer_v3_attr_tag' app_label = "qa" answer_id = models.IntegerField(verbose_name="回答id", db_index=True) attr_tag_id = models.IntegerField(verbose_name="属性标签ID", db_index=True) class AnswerVote(models.Model): class Meta: app_label = 'qa' db_table = 'api_answer_vote' unique_together = ('user', 'answer') user = fields.MagicField(type=int, manager=UserManager, ttl=60 * 5, db_column="user_id") answer = models.ForeignKey(Answer, verbose_name=u"评论") unread = models.BooleanField(default=True) is_fake = models.BooleanField(default=False, verbose_name=u"是否机器人点赞") create_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now) update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True) def to_dict(self): data = { 'answer_id': self.answer_id, 'nickname': self.user.nickname, 'user_id': self.user_id, 'vote_time': get_timestamp_or_none(self.create_time), 'image': '', 'content': escape(self.answer.content), 'type': VOTEOBJECT.ANSWER, 'membership_level': self.user.membership_level, 'portrait': self.user.portrait, } return data class AnswerReply(models.Model): class Meta: app_label = 'qa' db_table = 'api_answer_reply' verbose_name = u'问答的评论' user = fields.MagicField(type=int, manager=UserManager, ttl=60 * 5, db_column="user_id") content = models.TextField(verbose_name='回答', null=False) answer = models.ForeignKey(Answer, verbose_name=u"话题回复", related_name='replys') first_reply = models.ForeignKey('self', related_name=u'fir_comment', verbose_name=u'一级回复id', null=True, blank=True, default=None) commented_reply = models.ForeignKey('self', related_name=u'comments', verbose_name=u'被评论的回复', null=True, blank=True, default=None) is_online = models.BooleanField(verbose_name='是否在线', default=True) like_num = models.IntegerField(verbose_name='点赞数量', default=0) create_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now) update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True) # is_spam字段已弃用,请使用spam_label is_spam = models.BooleanField(verbose_name=u'是否为垃圾回答', default=False) spam_label = models.SmallIntegerField(default=SPAM_LABEL.NORMAL, choices=SPAM_LABEL, verbose_name=u"spam标签") # from topic topicreply_id = models.IntegerField(verbose_name='帖子id', null=True, default=None, unique=True) is_read = models.BooleanField(verbose_name='是否已读', default=False) is_fake = models.BooleanField(default=False, verbose_name=u"是否是灌水数据") @property def comment_num(self): return self.fir_comment.filter(is_online=True).count() def get_comment_data(self): if self.commented_reply and self.commented_reply.user == self.user: at_nickname = u'' elif self.commented_reply: at_nickname = self.commented_reply.user.nickname else: at_nickname = u'' return { 'content': escape(self.content), 'comment_id': self.id, 'comment_user_id': self.user.id, # 'comment_user_type': get_auth_type_by_userid(self.user_id), 'comment_user_type': USER_TYPE.NORMAL, "comment_user_gm_url": get_user_gm_url(self.user_id), 'nickname': self.user.nickname, 'at_user_id': self.commented_reply.user.id if self.commented_reply else 0, # 'at_user_type': get_auth_type_by_userid(self.commented_reply.user_id), 'at_user_gm_url': get_user_gm_url(self.commented_reply.user_id), 'at_user_type': USER_TYPE.NORMAL, # hotfix 修复评论名称点击跳转的问题 'at_nickname': escape(at_nickname), } def get_all_comments(self, need_newest=False, start_num=0, count=2, new_order=False, return_images=False): """ 获取全部子评论,引用时注意传参,默认获取全部的评论(老逻辑),考虑性能问题不要这么整…… need_newest 改成了 获取时间最早的两条评论 :param need_newest: 是否需要最新的 :param start_num: :param count: :param return_images: 是否返回评论图片 :return: """ comments = AnswerReply.objects.filter(first_reply=self, is_online=True) if need_newest and not new_order: comments = comments.order_by("-id")[start_num: start_num + count] if new_order: comments = comments.order_by("id")[start_num: start_num + count] result = [] comment_ids = [] for comment in comments: comment_ids.append(comment.id) result.append(comment.get_comment_data()) image_dict = defaultdict(list) if return_images: images = AnswerReplyImages.objects.filter(reply_id__in=comment_ids).values( 'reply_id', 'url', 'width', 'height', ) for image in images: image_dict[image['reply_id']].append({ 'image_url': image['url'], 'width': image['width'], 'height': image['height'], }) for item in result: item['images'] = image_dict.get(item['comment_id'], []) return result def get_data_for_reply(self, user=None, has_comment=False, need_newest=False, new_order=False, top_comment_id=None): is_vote = False if user: is_vote = AnswerVoteReply.objects.filter(user=user.id, answerreply=self).exists() data = { 'content': escape(self.content), 'reply_id': self.id, 'reply_date': get_timestamp_or_none(self.create_time), 'is_liked': is_vote, 'favor_amount': self.like_num, 'reply_count': self.comment_num, 'user_id': self.user.id, 'user_type': USER_TYPE.NORMAL, 'gm_url': get_user_gm_url(self.user.id), 'user_nickname': self.user.nickname, 'user_portrait': self.user.portrait, 'membership_level': self.user.membership_level, 'comments': [], 'user_level': get_user_level(self.user), "college_id": self.user.get("college_id", 0), "college_name": self.user.get("college_name", ""), } if need_newest and not new_order: data['comments'] = self.get_all_comments(need_newest=True, return_images=True) # 取最新两条 elif has_comment and not new_order: data['comments'] = self.get_all_comments(return_images=True) # 保留原始逻辑 elif new_order: data['comments'] = self.get_all_comments(new_order=new_order, return_images=True) # 回答的二级评论置顶 if top_comment_id: second_level_comment = AnswerReply.objects.filter(id=top_comment_id, is_online=True).first() if second_level_comment: data['comments'] = [item for item in data['comments'] if item.get("comment_id", 0) != int(top_comment_id)] data['comments'].insert(0, second_level_comment.get_comment_data()) return data def reply_data_after_create(self): photo = get_thumb_path(u'img%2Fuser_portrait.png') portrait = get_thumb_path(self.user.portrait) if self.user.portrait else photo user_type = USER_TYPE.NORMAL reply_data = { 'reply_id': self.id, 'user_type': user_type, 'user_id': self.user_id, 'reply_user_id': self.answer.user_id, 'user_nickname': self.user.nickname or u'昵称未设置', 'user_portrait': portrait, 'content': escape(self.content), 'reply_date': get_humanize_datetime(self.create_time), 'reply_timestamp': get_timestamp(self.create_time), 'comments': [], 'reply_count': self.comment_num, 'vote_count': self.like_num, } return reply_data def comment_data_after_create(self): if self.commented_reply and self.commented_reply.user == self.user: at_nickname = u'' elif self.commented_reply: at_nickname = self.commented_reply.user.nickname else: at_nickname = u'' user_type = USER_TYPE.NORMAL data = { 'comment_id': self.id, 'nickname': self.user.nickname, 'reply_id': self.commented_reply and self.commented_reply.id or '', 'at_nickname': escape(at_nickname), 'at_user_id': self.commented_reply.user_id if self.commented_reply else 0, 'at_user_type': user_type, 'comment_user_id': self.user.id, 'comment_user_type': get_auth_type_by_userid(self.user_id), 'content': escape(self.content), 'reply_count': self.comments.count(), 'vote_count': self.like_num, } return data def get_reply_for_mine(self): reply_data = { 'user': { 'doctor_name': '', 'user_name': '', 'doctor_id': '', 'user_id': self.user_id, 'user_portrait': '', 'doctor_portrait': '', }, 'reply': { 'replied_topic_id': self.commented_reply and self.commented_reply.id or '', 'content': escape(self.content), 'reply_id': self.id, 'commented_reply_id': self.commented_reply and self.commented_reply.id or '', 'reply_date': get_timestamp_or_none(self.create_time), 'image': '', 'is_new': not self.is_read, 'commented_content': escape(self.commented_reply.content) if self.commented_reply else None, 'commented_author_id': self.commented_reply.user_id if self.commented_reply else None, }, 'answer': { 'user': { 'nickname': '', 'id': self.answer.user_id, 'portrait': '', }, 'id': self.answer_id, 'title': self.answer.content, 'is_deleted': not self.answer.is_online }, 'topic': None, "diary": None, } return reply_data @classmethod def get_replys(cls, answer_id, last_id, size): """ 获取一级评论和次级评论,一级评论按照时间先后排序,次级评论紧跟一级评论 :return: """ replys = list() top_replys = list(cls.objects.filter( answer_id=answer_id, first_reply__isnull=True, is_online=True, id__gt=last_id )[0: size]) # 依次查一级评论下二级评论,总数够size则返回 for top_reply in top_replys: replys.append(top_reply) if len(replys) == size: break # 查询当前一级评论的次级评论 sub_replys = list(cls.objects.filter( answer_id=answer_id, first_reply_id=top_reply.id, is_online=True, )[0: size - len(replys)]) replys.extend(sub_replys) if len(replys) == size: break return replys def to_dict(self): return { 'id': self.id, 'user_id': self.user_id, 'user_name': self.user.nickname, 'content': escape(self.content), 'answer_id': self.answer_id, 'first_reply': self.first_reply_id, 'commented_reply': self.commented_reply_id, 'is_online': self.is_online, 'like_num': self.like_num, 'create_time': get_timestamp_or_none(self.create_time), 'topicreply_id': self.topicreply_id, 'is_read': self.is_read, 'is_fake': self.is_fake, } @classmethod def get_sub_reply_ids(cls, reply_id): reply_ids = cls.objects.filter(commented_reply_id=reply_id, is_online=True).values_list('id', flat=True) sub_reply_ids = [] sub_reply_ids.extend(reply_ids) for reply_id in reply_ids: sub_reply_ids.extend(cls.get_sub_reply_ids(reply_id)) return sub_reply_ids class AnswerVoteReply(models.Model): class Meta: app_label = 'qa' db_table = 'api_answerreply_vote' unique_together = ('user', 'answerreply') user = fields.MagicField(type=int, manager=UserManager, ttl=60 * 5, db_column="user_id") answerreply = models.ForeignKey(AnswerReply, verbose_name=u"评论") unread = models.BooleanField(default=True) create_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now) update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True) def to_dict(self): data = { 'reply_id': self.answerreply_id, 'nickname': "", 'user_id': self.user_id, 'vote_time': get_timestamp_or_none(self.update_time), 'membership_level': "", 'type': VOTEOBJECT.ANSWER_REPLY, 'portrait': "", } return data class UserAnswerQuestion(models.Model): """ 过渡方案,用于用户的问题与答案混排 """ class Meta: app_label = 'qa' db_table = 'api_user_question_answer' verbose_name = u'用户问答混排表' user = fields.MagicField(type=int, manager=UserManager, ttl=60 * 5, db_column="user_id") answer = models.ForeignKey(Answer, verbose_name=u'用户回答', blank=True, null=True) question = models.ForeignKey(Question, verbose_name=u'用户提问', blank=True, null=True) create_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now) is_online = models.BooleanField(verbose_name=u'问答及其相关是否在线', default=True) class AnswerTop(models.Model): class Meta: app_label = 'qa' db_table = 'api_answer_top' question = models.ForeignKey(Question, default=None) answer = models.ForeignKey(Answer, default=None) order = models.IntegerField('展示顺序', unique=True, default=0) top_type = models.IntegerField('置顶类型', default=0) start_time = models.DateTimeField('上线时间', default=None) end_time = models.DateTimeField('下线时间', default=None) enable = models.BooleanField('是否上线', default=True) create_time = models.DateTimeField(auto_now_add=True) class ZoneManager(RPCMixin): def __call__(self, pk_list): return self.call_rpc('api/zone/list', pks=pk_list) class OverHeadAnswer(models.Model): class Meta: app_label = 'qa' db_table = "api_overheadanswer" verbose_name = u'圈子推荐回答' zone = fields.MagicField(type=int, manager=ZoneManager, ttl=60, db_column="zone_id") answer = models.ForeignKey(Answer, help_text=u'回答', null=False, default='') rank = models.IntegerField(help_text=u'排序顺序', null=False, default=0) created_time = models.DateTimeField(help_text=u'创建时间', auto_now_add=True) deleted = models.BooleanField(help_text=u'是否已删除', default=False) class OverHeadQuestion(models.Model): class Meta: app_label = 'qa' db_table = "api_overheadquestion" verbose_name = u'圈子推荐问题' zone = fields.MagicField(type=int, manager=ZoneManager, ttl=60, db_column="zone_id") question = models.ForeignKey(Question, help_text=u'问题', null=False, default='') rank = models.IntegerField(help_text=u'排序顺序', null=False, default=0) created_time = models.DateTimeField(help_text=u'创建时间', auto_now_add=True) deleted = models.BooleanField(help_text=u'是否已删除', default=False) class QuestionDoctor(models.Model): class Meta: app_label = 'qa' db_table = 'api_question_doctor' verbose_name = u'问题tag相关联的美购智能推荐前20个医生' question = models.ForeignKey(Question) doctor_id = models.CharField(max_length=100, verbose_name=u'医生ID') unread = models.BooleanField(default=True, verbose_name=u'消息是否已读') create_time = models.DateTimeField(auto_now_add=True) class QuestionInviter(models.Model): class Meta: app_label = 'qa' db_table = "api_question_inviter" verbose_name = u'邀请回答' user = fields.MagicField(type=int, manager=UserManager, ttl=60 * 5, db_column="user_id", verbose_name="邀请人") # 邀请人 inviter = fields.MagicField(type=int, manager=UserManager, ttl=60 * 5, db_column="inviter_id", verbose_name="被邀请人") # 被邀请人 invite_time = models.DateTimeField(verbose_name="邀请时间", auto_now_add=True) question = models.ForeignKey(Question, verbose_name="被邀请的问题", related_name="invite_question", null=True, blank=True) answer = models.ForeignKey(Answer, verbose_name="被邀请的问题回答", related_name="invited", null=True, blank=True) user_read = models.BooleanField(verbose_name="邀请者对被邀请人的回答,是否已读", default=False) class QuestionAnswer(models.Model): class Meta: app_label = 'doris' db_table = "question_answer" verbose_name = u'最佳回答' question_id = models.CharField(max_length=64, verbose_name="问题id") answer_id = models.CharField(max_length=64, verbose_name="回答id") class InterestForContentUser(models.Model): class Meta: app_label = "qa" db_table = "interest_for_content_user" verbose_name = u'对内容有兴趣的用户' business_id = models.IntegerField(verbose_name="业务id") business_type = models.CharField(verbose_name="业务类型", max_length=64) handle_date = models.DateField(verbose_name="业务产生日期") push_type = models.IntegerField( verbose_name="push 类型", default=AUTOMATED_PUSH.BASE_USER_INTEREST_FOR_CONTENT) device_id = models.CharField(verbose_name="设备id", max_length=128) related_id = models.IntegerField(verbose_name="关联id") related_type = models.CharField(verbose_name="关联业务类型", max_length=64) score = models.FloatField(verbose_name="smart_rank") sort_index = models.IntegerField(verbose_name="排序") status = models.IntegerField(verbose_name="推送状态", default=0) create_time = models.DateTimeField(verbose_name=u'创建时间', auto_now_add=True) update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True) class QuestionFavor(models.Model): class Meta: app_label = 'qa' db_table = "api_question_favor" verbose_name = u'问题收藏' index_together = ['user_id', 'question_id'] user_id = models.IntegerField(verbose_name='收藏者id') question_id = models.IntegerField(db_index=True) create_time = models.DateTimeField(default=timezone.now()) update_time = models.DateTimeField(auto_now=True) is_online = models.BooleanField(default=True) is_read = models.BooleanField(default=False) class AnswerFavor(models.Model): class Meta: app_label = 'qa' db_table = "api_answer_favor" verbose_name = u'回答收藏' index_together = ['user_id', 'answer_id'] user_id = models.IntegerField(verbose_name='收藏者id') answer_id = models.IntegerField(db_index=True) create_time = models.DateTimeField(default=timezone.now()) update_time = models.DateTimeField(auto_now=True) is_online = models.BooleanField(default=True) is_read = models.BooleanField(default=False) class AnswerReplyImages(models.Model): class Meta: app_label = 'qa' db_table = 'api_answer_reply_images' verbose_name = u'回答评论图片' url = ImgUrlField(img_type=IMG_TYPE.TOPICREPLY, max_length=300, verbose_name=u'图片地址') width = models.IntegerField(verbose_name="图片宽度", default=0) height = models.IntegerField(verbose_name="图片高度", default=0) create_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now) update_time = models.DateTimeField(verbose_name=u'更新时间', auto_now=True) reply_id = models.IntegerField('评论ID', db_index=True) class StrategyAnswerSmrScore(models.Model): class Meta: db_table = 'strategy_answer_new_smart_rank_score' answer_id = models.IntegerField(unique=True) smart_rank_score = models.FloatField(verbose_name=u'新的smr_score', blank=True, default=0) create_date = models.BigIntegerField(max_length=50, verbose_name="时间") class SrategyAnswerSmrScore(models.Model): class Meta: db_table = 'strategy_answer_smr_score' answer_id = models.IntegerField(unique=True) smart_rank_score = models.FloatField(verbose_name=u'新的smr_score', blank=True, default=0) create_date = models.BigIntegerField(max_length=50, verbose_name="时间") class SearchStrategyAnswerSmrScore(models.Model): class Meta: db_table = 'search_strategy_answer_new_smart_rank_score' answer_id = models.IntegerField(unique=True) smart_rank_score = models.FloatField(verbose_name=u'新的smr_score', blank=True, default=0) create_date = models.BigIntegerField(max_length=50, verbose_name="时间") new_goodclick = models.FloatField(verbose_name=u'新的点击分', blank=True, default=0) smart_rank_v2 = models.FloatField(verbose_name=u'新的smr_score', blank=True, default=0) class CommunitysStrategyAnswerSmrScore(models.Model): class Meta: db_table = 'communitys_strategy_answer_smr_score' answer_id = models.IntegerField(unique=True) smart_rank_score = models.FloatField(verbose_name=u'新的smr_score', blank=True, default=0) create_date = models.BigIntegerField(max_length=50, verbose_name="时间") good_click = models.FloatField(verbose_name=u'热度', blank=True, default=0) class CommunitysStrategyAnswerHotScore(models.Model): class Meta: db_table = 'communitys_strategy_answer_hot_score' answer_id = models.IntegerField(unique=True) create_date = models.BigIntegerField(max_length=50, verbose_name="时间") hot_score = models.FloatField(verbose_name=u'热度', blank=True, default=0)