# -*- coding: utf-8 -*- from __future__ import unicode_literals, absolute_import, print_function import re import logging import traceback import datetime from cached_property import cached_property 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 from gm_types.mimas import ( SPAM_LABEL, GRABBING_PLATFORM, QUESTION_TYPE, SEND_ANSWER_STATUS, QA_CONTENT_TYPE, MEDIA_IMAGE_URL_SOURCE, IMAGE_TYPE, ) from gm_types.mimas.qa import CONTENT_CLASS, VIDEO_SOURCE_TYPE from gm_upload import ImgUrlField, IMG_TYPE from talos.services import UserConvertService from utils.rpc import RPCMixin from qa.cache import ViewRecord 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 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 PageGoodClick(models.Model): class Meta: db_table = 'al_community_detail_page_goodclick_v2' app_label = 'doris' page_name = models.CharField(max_length=50) business_id = models.CharField(max_length=50) detail_uv = models.IntegerField() detail_gc = models.IntegerField() detail_new_gc = models.FloatField() avg_new_gc = models.FloatField() detail_pv = models.IntegerField() create_date = models.CharField(max_length=50) 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 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格式数据不同 新增类型 # v 7.7.10 新增内容类型字段 枚举值 用于数据区分及召回 content_type = models.CharField(u'内容类型', max_length=2, choices=QA_CONTENT_TYPE, default=QA_CONTENT_TYPE.ORDINARY) @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)) # 问题本身的浏览量 num2 = sum([int(answer.view_amount) for answer in self.answers.filter(is_online=True)]) # 所有回答的浏览量 return (num1 + num2) * 13 @property def answer_num(self): return self.answers.filter(is_online=True).count() @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.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 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(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 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) # 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) @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 def data_for_qa(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 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), 'user_name': self.user.nickname, 'user_portrait': self.user.portrait, 'membership_level': self.user.membership_level, 'vote_num': self.like_num, 'comment_num': self.comment_num, 'view_num': self.view_amount, 'answer_id': str(self.id), 'question_id': str(self.question_id), 'content_images': self.content_images, 'video_cover_list': video_cover_list, 'is_voted': is_vote, 'user_level': get_user_level(self.user), '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 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: 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 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 == CONTENT_CLASS.EXCELLENT: 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 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 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 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) @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): """ 获取全部子评论,引用时注意传参,默认获取全部的评论(老逻辑),考虑性能问题不要这么整…… need_newest 改成了 获取时间最早的两条评论 :param need_newest: 是否需要最新的 :param start_num: :param count: :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 = [] for comment in comments: result.append(comment.get_comment_data()) return result def get_data_for_reply(self, user=None, has_comment=False, need_newest=False, new_order=False): 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) # 取最新两条 elif has_comment and not new_order: data['comments'] = self.get_all_comments() # 保留原始逻辑 elif new_order: data['comments'] = self.get_all_comments(new_order=new_order) 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 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")