# coding=utf8 from __future__ import unicode_literals, absolute_import, print_function import datetime import json from django.conf import settings from django.db import models from django.db.models import Count, Q from django.utils import timezone from django.utils.functional import cached_property from django.utils.html import escape from gm_types.gaia import AUTHOR_TYPE from gm_types.gaia import CASH_BACK_STATUS from gm_types.gaia import DIARY_AUDIT_STATUS from gm_types.gaia import DIARY_CONTENT_LEVEL from gm_types.gaia import DIARY_OPERATE from gm_types.gaia import DOCTOR_TYPE from gm_types.gaia import IMAGE_TYPE from gm_types.gaia import PROBLEM_FLAG_CHOICES from gm_types.gaia import PROBLEM_REVIEW_STATUS_CHOICES from gm_types.gaia import TAG_TYPE from gm_types.gaia import VOTEOBJECT from gm_types.gaia import GROUPBUY_STATUS, CONST_STRINGS from gm_upload import IMG_TYPE, ImgUrlField, batch_move_watermark_pic_to_no_watermark from social.utils import get_social_info from talos.cache.base import page_cache from talos.cache.base import diary_pv_cache from talos.cache.viewrecord import ViewRecord from talos.libs.datetime_utils import get_timestamp from talos.libs.datetime_utils import get_timestamp_or_none from talos.libs.image_utils import get_full_path from talos.libs.image_utils import get_short_path, get_temp_image_path from talos.rpc import logging_exception from talos.services.doctor import DoctorService from talos.services.goods import GoodsService from talos.services.hospital import HospitalService from talos.services.order import OrderService from talos.services.tag import TagService from talos.services.user import UserService from talos.services.tag_v3 import TagV3Service from utils.common import convert_image from utils.cache import common_cache_wrapper # 置顶优先级, 医生后台指定日记在其美购详情页跳转的日记列表展示 MAX_STICK_PRIORITY = 3 DEFAULT_STICK_PRIORITY = 255 class Diary(models.Model): """ 日记本 """ class Meta: verbose_name = u'日记本' verbose_name_plural = u'日记本' app_label = 'talos' db_table = 'api_diary' title = models.CharField(verbose_name=u'标题', max_length=128, default='') user_id = models.IntegerField() order_id = models.CharField(null=True, blank=True, verbose_name=u'关联的订单外键id', max_length=12) service_id = models.IntegerField(null=True, blank=True, verbose_name=u'关联的美购外键id') doctor_id = models.CharField(max_length=100, null=True, blank=True, verbose_name=u'关联的医生外键id') hospital_id = models.CharField(max_length=100, null=True, blank=True, verbose_name=u'关联的医院外键id') raw_doctor = models.CharField(verbose_name=u'医生(名称)', blank=True, default='', max_length=200) raw_hospital = models.CharField(verbose_name=u'医院(名称)', blank=True, default='', max_length=200) rating = models.FloatField(verbose_name=u'评价', blank=True, default=0) operation_effect_rating = models.FloatField(verbose_name=u'术后效果评分', blank=True, default=0) doctor_attitude_rating = models.FloatField(verbose_name=u'医生态度评分', blank=True, default=0) hospital_env_rating = models.FloatField(verbose_name=u'医院环境评分', blank=True, default=0) comment = models.TextField(verbose_name=u'评价文字', blank=True, default='') price = models.IntegerField(verbose_name=u'价格', blank=True, default=0) created_time = models.DateTimeField(verbose_name=u'创建时间', default=timezone.now) last_modified = models.DateTimeField(verbose_name=u'最后修改时间', default=timezone.now) last_topic_add_time = models.DateTimeField(verbose_name=u'最后发帖时间', blank=True, null=True, default=None) operation_time = models.DateTimeField(verbose_name=u'手术时间', blank=True, null=True, default=None) is_fake_operation_time = models.BooleanField(verbose_name=u'手术时间为假', default=False, help_text=u'当用户选择忘记了手术日期的时候,此字段为真') pre_operation_image = ImgUrlField(img_type=IMG_TYPE.DIARY, verbose_name=u'术前图', max_length=128, default='', blank=True, null=True) post_operation_image = ImgUrlField(img_type=IMG_TYPE.DIARY, verbose_name=u'术后图', max_length=128, default='', blank=True, null=True) is_headline = models.BooleanField(verbose_name=u"首页推荐", default=False, db_index=True) headline_time = models.DateTimeField( verbose_name=u'设为首页推荐的时间', null=True, default=None, help_text='如果取消首页推荐, 该字段设为None', ) sell_point = models.CharField(verbose_name=u'卖点', max_length=32, default='') is_online = models.BooleanField(verbose_name=u'是否上线', default=True, db_index=True) is_real_case = models.BooleanField(verbose_name=u'真人案例', default=False) is_sink = models.BooleanField(verbose_name=u'是否下沉', default=False, db_index=True) is_spam = models.BooleanField(verbose_name=u'是否为疑似广告', default=False, db_index=True) note = models.TextField(verbose_name=u'注释', max_length=200, default='', blank=True) reply_num = models.IntegerField(verbose_name=u'回复数量', default=0) share_num = models.IntegerField(verbose_name=u'分享数量', default=0) is_identification = models.BooleanField(verbose_name=u"是否认证", default=False, db_index=True) is_essence = models.BooleanField(default=False, help_text=u"是否精华帖", verbose_name=u"精华帖") # TODO Deprecated since 1.6.0 pre_operation_image_for_doctor = ImgUrlField( img_type=IMG_TYPE.DIARY, verbose_name=u'医生术前图', max_length=128, default=None, blank=True, null=True ) # TODO Deprecated since 1.6.0 post_operation_image_for_doctor = ImgUrlField( img_type=IMG_TYPE.DIARY, verbose_name=u'医生术后图', max_length=128, default=None, blank=True, null=True ) # 医生版1.9.1添加,每个医生的帖子列表中只能有一个置顶的 2016-02-23 sticky_post = models.BooleanField(default=False, help_text=u"是否置顶", verbose_name=u"置顶帖") # added at 5.8 like_num = models.IntegerField(default=0, verbose_name=u"赞的数目") # added at 5.9.3 # http://wiki.gengmei.cc/pages/viewpage.action?pageId=1842494 rate_count = models.IntegerField(default=0, verbose_name=u'评价次数', blank=True, null=False) is_choice = models.BooleanField(verbose_name=u'是否是精选日记本', default=False) is_choice_content = models.TextField(verbose_name=u'精选日记本描述', default='', blank=True) audit_status = models.CharField(verbose_name=u'审核状态', max_length=1, choices=DIARY_AUDIT_STATUS, default=DIARY_AUDIT_STATUS.UNAUDITED) audit_time = models.DateTimeField( verbose_name=u'最终审核时间', null=True, default=None, help_text='', ) content_level = models.CharField(verbose_name=u'内容等级', max_length=3, choices=DIARY_CONTENT_LEVEL, default=DIARY_CONTENT_LEVEL.UNAUDITED) stick_priority = models.PositiveIntegerField(verbose_name=u'置顶优先级', default=DEFAULT_STICK_PRIORITY) is_import = models.BooleanField(verbose_name=u'是否是导入数据', default=False, blank=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) @classmethod @common_cache_wrapper('diary:{diary_id}') def get_by_id(cls, diary_id): diary = Diary.objects.get(pk=diary_id) return diary @classmethod def get_topic_ids(cls, diary_id): from talos.models.topic import Problem if not diary_id: return [] topic_ids = Problem.objects.filter( diary_id=diary_id, flag=PROBLEM_FLAG_CHOICES.NORMAL, is_online=True, ).order_by('-operation_date', '-id').values_list('id', flat=True) return topic_ids @classmethod def get_topic_query(cls, diary_id): from talos.models.topic import Problem query = Problem.objects.filter( diary_id=diary_id, flag=PROBLEM_FLAG_CHOICES.NORMAL, is_online=True, ) return query def get_index_rank(self): """获取首页排序评分(Tuple3) """ # 热度分 try: heat_score = self.diary_rank.heat_score # calculate the loss caused by time from talos.libs.diaryrank_utils import DiaryRankTool loss = DiaryRankTool.get_loss_score_by_time(self.id, heat_score) heat_score_now = heat_score - loss heat_score = max(0, min(heat_score_now, settings.DIARY_HEAT_RANK['max_score'])) except Exception: heat_score = 0.0 # 质量分 if self.diary_check.exists(): audit = self.diary_check.all().order_by('id').last() audit_score_map = { DIARY_CONTENT_LEVEL.UNAUDITED: settings.DIARY_CONTENT_LEVEL_SCORE['UNAUDITED'], DIARY_CONTENT_LEVEL.ILLEGAL: settings.DIARY_CONTENT_LEVEL_SCORE['ILLEGAL'], DIARY_CONTENT_LEVEL.BAD: settings.DIARY_CONTENT_LEVEL_SCORE['BAD'], DIARY_CONTENT_LEVEL.GENERAL: settings.DIARY_CONTENT_LEVEL_SCORE['GENERAL'], DIARY_CONTENT_LEVEL.FINE: settings.DIARY_CONTENT_LEVEL_SCORE['FINE'], DIARY_CONTENT_LEVEL.EXCELLENT: settings.DIARY_CONTENT_LEVEL_SCORE['EXCELLENT'], DIARY_CONTENT_LEVEL.BETTER: settings.DIARY_CONTENT_LEVEL_SCORE['BETTER'], } audit_score = audit_score_map[audit.content_level] else: audit_score = 0.0 # 医生抽成分 choucheng_score = DoctorService.get_discount_score_for_diary_index(self.doctor_id) return heat_score, audit_score, choucheng_score @property def comment_count(self): return self.rate_count @property def has_commented(self): """日记本评价""" return bool(self.rating or self.comment) @property def title_style_type(self): return '' def tags_sort_for_special(self, tags): result = self.get_tags_from_tags_by_types( tags, [ TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM, TAG_TYPE.ITEM_WIKI ] ) result += self.get_tags_from_tags_by_types( tags, [ TAG_TYPE.CITY, TAG_TYPE.PROVINCE, TAG_TYPE.COUNTRY ] ) result += self.get_tags_from_tags_by_types(tags, [TAG_TYPE.FREE]) return result def get_tags_from_tags_by_types(self, tags, types): result = [] for tag in tags: if tag.name in settings.UNSHOW_TAGS: continue if tag.tag_type in types: result.append(tag) return result @cached_property @common_cache_wrapper('diary:tags_new_era:{diary_id}', key_args=[('diary_id', 'self', lambda x: x.id)]) def tags_new_era(self): result = [] for tag in self.tags: # 添加热门tag后 跟日记本相关的显示部分 热门tag作为3级tag来显示 d = {'name': tag.name, 'id': tag.id, 'type': tag.tag_type, 'tag_id': tag.id} if hasattr(tag, 'itemwiki'): d['type'] = TAG_TYPE.ITEM_WIKI d['wiki_id'] = tag.itemwiki result.append(d) result = result[0:10] return result @cached_property def wiki_info(self): result = [] for tag in self.tags_new_era: if 'wiki_id' not in tag: continue obj = { 'id': tag['wiki_id'], 'name': tag['name'], 'tag_id': tag['id'], } result.append(obj) return result @cached_property def operation_tags(self): # get all operation tags tags = filter(lambda x: x.tag_type in (TAG_TYPE.ITEM_WIKI, TAG_TYPE.BODY_PART_SUB_ITEM), self.tags) return tags @classmethod def get_redis_diary_topic_vote_key(cls): topic_vote = "diary_topic_vote" return topic_vote @cached_property @common_cache_wrapper('diary:vote_num:{diary_id}', key_args=[('diary_id', 'self', lambda x: x.id)]) def vote_num(self): # TODO: 为了性能转成预处理 # 医生版2.1.0 把评论的赞数也统计上了 2016-04-20 # 日记贴里面把获取点赞数逻辑放入了redis,日记本取的是原生,是否考虑将 # 日记本的点赞逻辑也放入redis # 6.5.0 用户感知去掉 # comment_votes = sum(int(reply.like_num) for reply in self.topicreply_set.all()) topic_ids = list(self.topics.all().values_list("id", flat=True)) topic_vote_num_list = list(filter(None, ViewRecord(CONST_STRINGS.TOPIC_VOTE_V1).view_hmget(topic_ids))) num2 = sum(map(int, topic_vote_num_list)) if topic_vote_num_list else 0 return num2 + self.like_num @property def cover_key(self): return "diary:enduring_cover:{diary_id}".format(diary_id=self.id) @cached_property def enduring_cover(self): data = page_cache.get(self.cover_key) if data is None: cover = self.cache_cover() return cover return json.loads(data) def cache_cover(self, timeout=60*60*24*1): """缓存封面图""" cover_key = self.cover_key cover = self.cal_cover() page_cache.setex(cover_key, timeout, json.dumps(cover)) return cover @cached_property @common_cache_wrapper('diary:cover:{diary_id}', key_args=[('diary_id', 'self', lambda x: x.id)]) def cover(self): return self.cal_cover() def cal_cover(self): """ 封面 v5.4.0 重新整理 case1:日记本的所有贴子里术前术后图都至少有一张 case1:日记本术前图术后图都已经设置了,正常显示 case2:日记本术前图or术后图未设置,从所有贴子里取最新的术前或术后图补上,作为对比图封面 case2:整个日记本缺少术前图 case1:已经设置了日记本术后图,显示这张图片作为封面 case2:未设置日记本术后图,显示最新的术后图作为封面 case3:整个日记本缺少术后图 case1:已经设置了日记本术前图,显示这张图片作为封面 case2:未设置日记本术前图,显示最新的术前图作为封面 case4:日记本无图,列表里不显示图片 """ # 日记本封面图新逻辑,数据全部按照新版逻辑获取,潜在问题浩楠已知晓 from talos.models.topic.topicimage import TopicImage base_query = Q(topic__diary=self, topic__is_online=True, topic__flag=PROBLEM_FLAG_CHOICES.NORMAL) diary_operation_time = self.operation_time or self.created_time # 获取日记本术前封面图 if self.pre_operation_image: pre_image = self.pre_operation_image else: pre_operation_image = self.pre_operation_images.all().order_by("-is_cover", "-id").first() if pre_operation_image: pre_image = pre_operation_image.image_url else: query = base_query & Q(topic__operation_date__lte=diary_operation_time) pre_topic_image = TopicImage.objects.filter(query).order_by("topic__operation_date", "id").first() pre_image = pre_topic_image.image_url if pre_topic_image else "" # 获取日记本术后封面图 if self.post_operation_image: post_image = self.post_operation_image else: query = base_query & Q(topic__operation_date__gt=diary_operation_time) post_topic_image = TopicImage.objects.filter(query).order_by("-topic__operation_date", "-id").first() post_image = post_topic_image.image_url if post_topic_image else "" diary_cover = [] if pre_image: pre_operation_image_data = { 'image': get_full_path(pre_image), 'desc': 'Before' } pre_operation_image_data.update(self._format_pic_dict(pre_image)) diary_cover.append(pre_operation_image_data) if post_image: post_operation_image_data = { 'image': get_full_path(post_image), 'desc': 'After', } post_operation_image_data.update(self._format_pic_dict(post_image)) diary_cover.append(post_operation_image_data) return diary_cover @staticmethod def _format_pic_dict(image_url): return convert_image(image_url, watermark=True) def has_cover(self): return bool(self.cover) @common_cache_wrapper('diary:video_cover:{diary_id}', key_args=[('diary_id', 'self', lambda x: x.id)]) def video_cover(self): data = {'video_pic': '', 'video_url': '', 'short_video_url': ''} if not settings.SHOW_VIDEO_COVER: data.update({'video_cover': ''}) return data if self.latest_topic and self.latest_topic.can_video_cover: data = self.latest_topic.get_video_info() # 为客户端添加:`video_cover` 字段 data.update({'video_cover': data['video_pic']}) return data @property def operator_is_hospital_officer(self): return self.doctor and self.doctor.doctor_type == DOCTOR_TYPE.OFFICER or False @cached_property def doctor(self): if not self.doctor_id: return doctor = DoctorService.get_doctor_from_doctor_id(self.doctor_id) if not doctor: return return doctor def tag_count(self): return len(self.tags) @cached_property def related_service_name(self): if not self.order_id: return None order = OrderService.get_order_data_for_update_diary_operation_info(order_id=self.order_id) if order: return order['service_name'] else: return None def get_operation_info_for_topic_detail(self): """get diary operaiton info for topic detail. TODO: refactor topic detail page(pc, m, backend, doctor), get diary info from diary api instead of "api/topic" """ result = { 'operation_items': list(map( lambda x: {'tag_id': x.id, 'name': x.name, 'id': ''}, self.operation_tags )), 'rating': self.rating or 0, 'price': self.price, 'operation_timestamp': None, 'comment': self.comments, 'service_id': '', 'service_name': '', 'hospital_name': self.raw_hospital or u'', 'doctor_id': '', 'doctor_name': self.raw_doctor or u'', } if self.operation_time: result['operation_timestamp'] = get_timestamp(self.operation_time) if self.order_id: # if diary related to an order, get doctor/service info from order order = OrderService.get_order_data_for_update_diary_operation_info(order_id=self.order_id) if order: result['service_id'] = order['service_id'] result['service_name'] = order['service_name'] result['hospital_name'] = order['hospital_name'] result['doctor_id'] = order['doctor_id'] result['doctor_name'] = order['doctor_name'] else: # get service doctor hospital by related id(diary.doctor_id, diary.hospital_id) if self.doctor: result['doctor_id'] = self.doctor.id result['doctor_name'] = self.doctor.name if self.hospital_id: hospital = HospitalService.get_hospital_from_hospital_id(self.hospital_id) if hospital: result['hospital_name'] = hospital.name return result def _set_service_info(self, info, service): if service is not None: info['service_id'] = service.id info['service_name'] = service.name info['service_short_description'] = service.short_description info['service_image'] = service.image_header def _set_doctor_info(self, info, service): doctor = DoctorService.get_doctor_from_doctor_id(self.doctor_id) if doctor is not None: info['doctor_id'] = doctor.id info['doctor_name'] = doctor.name info['doctor_is_online'] = doctor.is_online info['doctor_portrait'] = doctor.portrait info['doctor_title'] = doctor.title # 新加医生职称字段 else: info['doctor_id'] = '' info['doctor_name'] = self.raw_doctor or u'' info['doctor_portrait'] = '' info['doctor_title'] = '' if info['doctor_portrait'] == '' and service is not None: d_id = service.doctor.id d = DoctorService.get_doctor_from_doctor_id(d_id) if d is not None: info['doctor_portrait'] = d.portrait else: info['doctor_portrait'] = '' def _set_hospital_info(self, info): hospital = HospitalService.get_hospital_from_hospital_id(self.hospital_id) if hospital is not None: info['hospital_id'] = hospital.id info['hospital_name'] = hospital.name else: info['hospital_id'] = '' info['hospital_name'] = self.raw_hospital or u'' def _set_order_info(self, info, s): order = OrderService.get_order_from_order_id(self.order_id) if order is not None: if s: info['order_info'] = { 'title': s.name, 'image': s.image_header, 'order_id': order.id, 'validate_time': get_timestamp_or_none(order.validate_time), 'groupbuy_status': order.groupbuy_status, } else: info['order_info'] = {} info['order_id'] = order.id if not info['service_id']: ss = json.loads(order.service_snapshot) if ss is None: logging_exception() info['service_id'] = ss.get('id', '') info['service_name'] = ss.get('name', '') info['service_short_description'] = ss.get('short_description', '') info['service_image'] = ss.get('image_header', '') doctor = DoctorService.get_doctor_from_doctor_id(ss.get('doctor')) if doctor is None: info['operator_is_hospital_officer'] = False else: info['operator_is_hospital_officer'] = True if doctor.doctor_type == DOCTOR_TYPE.OFFICER else False else: info['order_id'] = '' info['order_info'] = {} def _get_all_pre_operation_images(self): pre_operation_images = self.pre_operation_images.all() images = [] temp_image = [] for image in pre_operation_images: is_cover = False if self.pre_operation_image: if image.image_url == self.pre_operation_image or image.cover_image_url == self.pre_operation_image: is_cover = True elif image.is_cover: is_cover = True image_info = image.get_image_info() image_info['is_cover'] = is_cover temp_image.append(get_short_path(image.image_url)) images.append(image_info) if temp_image: new_images = batch_move_watermark_pic_to_no_watermark(temp_image, 'wanmeizhensuo', 'wanmeizhensuo-tmp') i = 0 for image_info in images: if image_info['image_name'] in new_images: image_info['source_image'] = get_temp_image_path(image_info['image_name']) else: image_info['source_image'] = image_info['image'] i += 1 return images def _set_operation_image_info(self, info): info['pre_operation_images'] = self._get_all_pre_operation_images() amount = len(info['pre_operation_images']) info['pre_operation_image_amount'] = u'{} 张'.format(amount) if amount else u'' if self.pre_operation_image: info['pre_operation_image'] = { 'image': get_full_path(self.pre_operation_image, '-w'), 'image_thumb': get_full_path(self.pre_operation_image, '-thumb'), 'image_half': get_full_path(self.pre_operation_image, '-half'), 'image_wide': get_full_path(self.pre_operation_image, '-wide'), 'desc': '' } else: info['pre_operation_image'] = {} if self.post_operation_image: info['post_operation_image'] = { 'image': get_full_path(self.post_operation_image, '-w'), 'image_thumb': get_full_path(self.post_operation_image, '-thumb'), 'image_half': get_full_path(self.post_operation_image, '-half'), 'image_wide': get_full_path(self.post_operation_image, '-wide'), 'desc': '' } else: info['post_operation_image'] = {} def _set_operation_info(self, info): if self.operation_time: info['operation_timestamp'] = get_timestamp(self.operation_time) if self.operation_time: interval = datetime.datetime.now() - self.operation_time if interval.days >= 0: info['interval'] = u'{}'.format(interval.days + 1) else: info['interval'] = u'' else: info['interval'] = u'' def operation_info(self): operation_item_amount = self.tag_count() info = { 'operation_item_amount': u'{} 项'.format(operation_item_amount) if operation_item_amount else u'', # TODO: delete id:'' @594, this is only for compatible 'operation_items': list(map( lambda x: {'tag_id': x.id, 'name': x.name, 'id': ''}, self.operation_tags )), 'rating': self.rating or 0, 'operation_effect_rating': self.operation_effect_rating or 0, 'doctor_attitude_rating': self.doctor_attitude_rating or 0, 'hospital_env_rating': self.hospital_env_rating or 0, 'price': self.price, 'operation_timestamp': None, 'operator_is_hospital_officer': self.operator_is_hospital_officer, 'comment_count': self.comment_count, } info['service_id'] = '' info['service_name'] = '' info['service_short_description'] = '' info['service_image'] = '' service = GoodsService.get_service_by_service_id(self.service_id) self._set_service_info(info, service) self._set_doctor_info(info, service) self._set_order_info(info, service) self._set_hospital_info(info) self._set_operation_image_info(info) self._set_operation_info(info) info['comment'] = self.comments info['has_topics'] = self.topics.filter(is_online=True, flag=PROBLEM_FLAG_CHOICES.NORMAL).count() > 0 info['operation_effect_rating'] = self.operation_effect_rating info['doctor_attitude_rating'] = self.doctor_attitude_rating info['hospital_env_rating'] = self.hospital_env_rating info['show_comment'] = self.has_commented info['comment_count'] = self.comment_count return info @property def comments(self): # 可供选择的评论 comments_choice = { 'good': [ {'name': u'效果非常好', 'select': 0}, {'name': u'医生技术好', 'select': 0}, {'name': u'医生态度好', 'select': 0}, {'name': u'医院环境好', 'select': 0}, {'name': u'价格超便宜', 'select': 0}, {'name': u'性价比很高', 'select': 0}, {'name': u'会推荐朋友', 'select': 0}, ], 'bad': [ {'name': u'还挺一般的', 'select': 0}, {'name': u'医院老推销', 'select': 0}, {'name': u'效果不理想', 'select': 0}, ] } if self.comment: return json.loads(self.comment) else: return comments_choice @property def pre_operation_images_info(self): return [get_full_path(i.image_url) for i in self.pre_operation_images.all()] def is_voted_by(self, user): from .diaryvote import DiaryVote if not user: return False try: DiaryVote.objects.get(user_id=user.id, diary=self) is_voted = True except DiaryVote.DoesNotExist: is_voted = False return is_voted @cached_property def latest_topic(self): latest_topic = self.topics.filter( is_online=True, flag=PROBLEM_FLAG_CHOICES.NORMAL ).select_related('video').order_by('-operation_date', '-id').first() # v 7.6.25 变更 日记帖取设置的术后日期离今天最近的一篇,时间相同取id最大 return latest_topic @property def author_type(self): return AUTHOR_TYPE.USER def get_simple_diary(self, user=None): social_info = get_social_info(user) is_following = social_info and social_info.is_following_user(uid=self.user_id) or False latest_topic = self.latest_topic latest_topic_id = latest_topic and latest_topic.id or 0 content = latest_topic and escape(latest_topic.answer) or '' hospital = HospitalService.get_hospital_from_hospital_id(self.hospital_id) if hospital is not None: hospital_name = hospital.name else: hospital_name = self.raw_hospital or u'' doctor = DoctorService.get_doctor_from_doctor_id(self.doctor_id) diary_author = UserService.get_user_by_user_id(self.user_id) # TO DO: # diary_num 在IOS客户端全局无使用, 安卓客户端某个不确定版本已废弃 # 当前最新线上版本v6.4.0,期望在某个版本迭代后将此字段删除 data = { 'title_style_type': self.title_style_type, 'reply_num': self.reply_num, 'is_following': is_following, 'city': diary_author.city_name, 'user_id': self.user_id, 'title': self.title if self.title else '', 'user_portrait': diary_author.portrait, 'latest_topic_id': latest_topic_id, 'diary_id': self.id, 'vote_num': self.vote_num, 'content': content, 'user_nickname': diary_author.nickname, 'author_type': AUTHOR_TYPE.USER, 'tags': self.tags_new_era, 'images': self.cover, 'sticky_post': self.sticky_post, 'membership_level': diary_author.membership_level, 'view_num': self.view_num, 'diary_amount': self.extra_info.topic_count if hasattr(self, 'extra_info') else 0, 'is_voted': self.is_voted_by(user), 'vote_num_gained': diary_author.vote_count, 'topic_num_posted': diary_author.topic_count, 'diary_num': self.topic_num, 'doctor_id': self.doctor_id, 'doctor_name': doctor and doctor.name, 'hospital_name': hospital_name, 'created_time': get_timestamp_or_none(self.created_time), # TO DO: # 以下字段为本次上线需要兼容backend做的冗余,下次迭代需删除 'is_identification': self.is_identification, 'tags_new_era': self.tags_new_era, 'date': get_timestamp_or_none(self.last_modified), 'user': { 'vote_num_gained': diary_author.vote_count, 'topic_num_posted': diary_author.topic_count, 'portrait': diary_author.portrait, 'user_name': diary_author.nickname, 'membership_level': diary_author.membership_level, 'city': diary_author.city_name, 'user_id': self.user_id, }, 'user_level': { 'membership_icon': user and user.membership_icon or '', 'level_icon': user and user.level_icon or '', 'constellation_icon': user and user.constellation_icon or '', }, # 以下为兼容医生版的字段, 下次迭代需跟医生版上来商量删除 'tags_info': [], 'rating': self.rating, # 给美购的评分 } return data def get_diary_info(self, simple=False, user=None): # NOTE: if simple is True, operation info wont be return # all diary list should call this method with simple=True social_info = None if user: from social.models import SocialInfo social_info = SocialInfo(uid=user.id) doctor = DoctorService.get_doctor_from_doctor_id(self.doctor_id) diary_author = UserService.get_user_by_user_id(user_id=self.user_id) diary_data = { 'user': { 'user_id': self.user_id, 'user_name': diary_author.nickname, 'portrait': diary_author.portrait, 'city': diary_author.city_name, 'membership_level': diary_author.membership_level, 'doctor_id': '', 'hospital_id': '', 'vote_num_gained': diary_author.vote_count, 'topic_num_posted': diary_author.topic_count, } } user_bz = UserService.get_doctor_hospital_id_by_user_id(self.user_id) if user_bz: diary_data['user_id'] = user_bz['user_id'] diary_data['doctor_id'] = user_bz['doctor_id'] diary_data['hospital_id'] = user_bz['hospital_id'] diary_data['raw_doctor'] = self.raw_doctor if self.raw_doctor else '' diary_data['raw_hospital'] = self.raw_hospital if self.raw_hospital else '' diary_data['diary_id'] = self.id diary_data['diary_amount'] = self.topics.filter(is_online=True).count() diary_data['reply_num'] = self.reply_num diary_data['reply_num_for_diary'] = self.topicreply_set.filter(is_online=True).count() diary_data['vote_num'] = self.vote_num diary_data['view_num'] = self.view_num # get doctor info hospital = HospitalService.get_hospital_from_hospital_id(self.hospital_id) diary_data['doctor_id'] = self.doctor_id or '' diary_data['doctor_name'] = doctor and doctor.name or '' diary_data['hospital_name'] = hospital and hospital.name or '' diary_data['hospital_id'] = self.hospital_id or '' latest_topic = self.topics.filter( is_online=True, flag=PROBLEM_FLAG_CHOICES.NORMAL ).last() diary_data.update(self.video_cover()) order = OrderService.get_order_from_order_id(self.order_id) if order is not None and order.cash_back_status == CASH_BACK_STATUS.SUCCESS: is_cash_back = True else: is_cash_back = False diary_data.update({ 'images': self.cover, 'title': self.title if self.title else '', 'last_modified': get_timestamp_or_none(self.last_modified), 'created_time': get_timestamp_or_none(self.created_time), 'is_identification': self.is_identification, 'is_headline': self.is_headline, 'is_essence': self.is_essence, 'is_online': self.is_online, 'is_sink': self.is_sink, 'is_cash_back': is_cash_back, 'is_favored': self.favor_diary_diary.filter(user_id=user.id, is_deleted=False).exists() if user else False, 'is_following': social_info and social_info.is_following_user(uid=self.user_id) or False, 'latest_topic_id': latest_topic and latest_topic.id or 0, 'tags_new_era': self.tags_new_era, 'tags': self.wiki_info, 'zones': [], 'tags_info': [], 'is_voted': self.is_voted_by(user), 'sticky_post': self.sticky_post, }) author_type = self.author_type diary_data.update({ 'title_style_type': self.title_style_type, 'author_type': author_type, 'content': latest_topic and escape(latest_topic.answer) or '', 'date': get_timestamp_or_none(self.last_modified), }) if simple: return diary_data diary_data['operation_info'] = self.operation_info() return diary_data @property def extra_diary_images(self): return [get_full_path(topic.image_url) for topic in self.topics.last().images.all()] def newest_cover(self): """ v5.3.0 获取日记本最新的帖子的图片 作为封面 规则: 1.最新的帖子 2.术后图 3.术前图 """ topics = self.topics.all().order_by('-created_time') image = settings.DIARY_COVER_DEFAULT for topic in topics: images = topic.images.all() if images: image = images[0].image_url break return image def get_simple_tags(self): tags = self.tags return tags[0:4] def simple_data(self): """ v5.3.0 """ is_service_diary = False tags = [t.name for t in self.get_simple_tags()] service = GoodsService.get_service_by_service_id(self.service_id) if service: tags = [service.name] is_service_diary = True return { 'is_service_diary': is_service_diary, 'id': self.id, 'topics_num': self.topics.filter(is_online=True, flag=PROBLEM_FLAG_CHOICES.NORMAL).count(), 'tags': tags, 'title': self.title, 'image': self.newest_cover(), 'created_time': get_timestamp(self.created_time), 'last_modified': get_timestamp(self.last_modified), 'review_count': self.review_count, 'vote_count': self.vote_num, 'comment_count': self.comment_count if self.order_id is not None else 0, 'type': VOTEOBJECT.DIARY, } def topics_data(self): topics = [] for topic in self.topics.filter(is_online=True, flag=PROBLEM_FLAG_CHOICES.NORMAL): topics.append( { "id": topic.id, "title": topic.get_title(), "review_status": topic.review_status, "review_reason": PROBLEM_REVIEW_STATUS_CHOICES.getDesc(topic.review_status) } ) return topics @property def cashback_topic_num_limit(self): service = GoodsService.get_service_by_service_id(self.service_id) if service: # self.service 不是NoneType时 return service.cashback_limit else: return 0 @property def review_count(self): return self.topics.filter(review_status=PROBLEM_REVIEW_STATUS_CHOICES.OK).count() @property def diary_first_topic(self): return self.topics.filter(is_online=True, flag=PROBLEM_FLAG_CHOICES.NORMAL).order_by('created_time').first() @property def view_num(self): num1 = diary_pv_cache.get(str(self.id)) topic_ids = list(self.topics.all().values_list("id", flat=True)) topic_view_num_list = list(filter(None, ViewRecord(CONST_STRINGS.TOPIC_VIEW).view_hmget(topic_ids))) num2 = sum(map(int, topic_view_num_list)) if topic_view_num_list else 0 if num1 is None: num1 = 0 if num2 is None: num2 = 0 return str(int(num1) + int(num2)) @cached_property def topic_num(self): num = self.topics.filter(flag=PROBLEM_FLAG_CHOICES.NORMAL, is_online=True).count() return num @property def get_diary_operate(self): from .diaryoperate import DiaryOperate ''' :return: 用户的更新操作 ''' if self.audit_time is not None: audit_time = self.audit_time else: audit_time = datetime.datetime.fromtimestamp(0) update_info_list = DiaryOperate.objects.filter( diary__id=self.id, operate_time__gte=audit_time, operation=DIARY_OPERATE.UPDATE_INFO, ).values('topic').annotate(Count('topic')) update_topic_list = DiaryOperate.objects.filter( diary__id=self.id, operate_time__gte=audit_time, operation=DIARY_OPERATE.UPDATE_TOPIC, ).values('topic').annotate(Count('topic')) delete_topic_list = DiaryOperate.objects.filter( diary__id=self.id, operate_time__gte=audit_time, operation=DIARY_OPERATE.DELETE_TOPIC, ).values('topic').annotate(Count('topic')) add_topic_list = DiaryOperate.objects.filter( diary__id=self.id, operate_time__gte=audit_time, operation=DIARY_OPERATE.ADD_TOPIC, ).values('topic').annotate(Count('topic')) read = u'' if len(update_info_list) != 0: read += u'更新术前资料\n' if len(update_topic_list) != 0: read += u'修改%s篇帖子\n' % str(len(update_topic_list)) if len(delete_topic_list) != 0: read += u'删除%s篇帖子\n' % str(len(delete_topic_list)) if len(add_topic_list) != 0: read += u'新增%s篇帖子\n' % str(len(add_topic_list)) return read[:-1] def set_diary_operate(self, operation, topic=''): from .diaryoperate import DiaryOperate if operation in DIARY_OPERATE: diary_operate = DiaryOperate() diary_operate.diary_id = self.id diary_operate.operate_time = datetime.datetime.now() diary_operate.operation = operation diary_operate.topic = topic diary_operate.save() if self.audit_status != DIARY_AUDIT_STATUS.UNAUDITED: self.audit_status = DIARY_AUDIT_STATUS.REAUDIT self.save() @cached_property @common_cache_wrapper('diary:tags:{diary_id}', key_args=[('diary_id', 'self', lambda x: x.id)]) def tags(self): # 日记本关联的tag数量有限的话 可以这么做 from .diarytag import DiaryTag tag_ids = DiaryTag.objects.filter(diary_id=self.id).values_list('tag_id', flat=True) tag_ids = list(tag_ids) tags = TagService.get_tags_by_tag_ids(tag_ids) tags = self.tags_sort_for_special(tags) return tags or [] @cached_property @common_cache_wrapper('diary:tag_v3:{diary_id}', key_args=[('diary_id', 'self', lambda x: x.id)]) def tags_v3(self): # tag v3 from .diarytag import DiaryTagV3 tag_ids = list(DiaryTagV3.objects.filter(diary_id=self.id).values_list('tag_v3_id', flat=True)) tags = TagV3Service.get_tags_by_tag_v3_ids(tag_ids) tags_info = [ TagV3Service.format_tag_v3(tag) for tag in tags.values() ] return tags_info @cached_property def all_tags(self): # 日记本关联的tag数量有限的话 可以这么做 from .diarytag import DiaryTag tag_ids = DiaryTag.objects.filter(diary_id=self.id).values_list('tag_id', flat=True) tag_ids = list(tag_ids) tags = TagService.get_tags_by_tag_ids(tag_ids) return tags or [] def add_tags(self, tag_ids): from .diarytag import DiaryTag for tag_id in tag_ids: DiaryTag.objects.get_or_create(diary_id=self.id, tag_id=tag_id) def upd_tags(self, tag_ids): new_tags = set([int(tag_id) for tag_id in tag_ids if str(tag_id).isdigit()]) old_tags = set([tag.id for tag in self.all_tags]) self.add_tags(list(new_tags - old_tags)) self.del_tags(list(old_tags - new_tags)) def del_tags(self, tag_ids): from .diarytag import DiaryTag DiaryTag.objects.filter(diary_id=self.id, tag_id__in=tag_ids).delete() def delete_all_tags(self): from .diarytag import DiaryTag DiaryTag.objects.filter(diary_id=self.id).delete() @property def is_operation(self): if not self.order_id: return False order = OrderService.get_order_data_for_update_diary_operation_info(order_id=self.order_id) if order: return order['is_operation'] else: return False @classmethod def load_by_id(cls, diary_id): try: return cls.get(pk=diary_id) except cls.DoesNotExist: return None @classmethod def list_by_ids(cls, diary_ids): return cls.objects.filter(pk__in=diary_ids) class DiaryRecord(models.Model): """ 日记本新增、修改记录 """ class Meta: verbose_name = u'日记本新增修改记录' verbose_name_plural = u'日记本新增修改记录' app_label = 'talos' db_table = 'api_diary_record' diary_id = models.IntegerField(verbose_name="日记id")