# coding=utf-8 from __future__ import unicode_literals, absolute_import, print_function import datetime, time import re from collections import defaultdict from django.db import transaction from django.db.models import Q, Count from django.utils.html import escape from gm_types.error import ERROR as CODES from gm_types.gaia import DIARY_OPERATE from gm_types.gaia import FILTER_WORD_TYPE from gm_types.gaia import PROBLEM_FLAG_CHOICES from gm_types.gaia import TAG_TYPE from gm_types.gaia import TOPIC_TYPE from gm_types.mimas import VOTE_TASK_TYPE from talos.manager.topic import topic_list_manager from talos.manager.topic_reply import comment_data_by_reply_ids from talos.models.diary.diary import Diary from talos.models.diary import DiaryExtra, DiaryFavor from talos.models.draft import Draft from talos.models.topic.topicreply import TopicReply from talos.models.topic.video import Video from talos.rpc import bind, bind_context from talos.services import DoctorService from talos.services import TagService from talos.services import UserService from talos.services import get_user_from_context from talos.services.user import User from talos.tasks.vote import process_topic_vote_for_create from talos.tools.fake_vote_tool import is_special_user, fake_vote_v1 from talos.views.topic import topic_specialized from utils.rpc import gen, logging_exception, get_current_user from talos.models.topic import ProblemTag, Problem, TopicImage, TopicScore, TopicVote, ProblemFavor from talos.libs.datetime_utils import get_timestamp_or_none from talos.tools.filterword_tool import filterword_by_custom from live.tasks import set_water_mark_to_video from utils.UploadVideoPicture import UploadVideoPicture from utils.group_routine import GroupRoutine # todo fix the cache @bind_context('topic/get/topic') def get_topic_info_detail(ctx, id): topic_data = topic_specialized.get_topic_info(ctx, id) tag_v3_infos = topic_list_manager.get_topic_tagv3_info_by_topic_ids([id]) topic_data["problem"].update({ "tags_v3": tag_v3_infos.get(id, []), }) return topic_data @bind_context('topic/create') def create_topic( ctx, topic_type_id, content, tag_ids=[], images=[], device_id=None, title=None, is_mixed=0, pure_words='', draft_id=None): """create discuss and consulting topic only.""" if topic_type_id not in (TOPIC_TYPE.ASK, TOPIC_TYPE.TOPIC): return gen(CODES.OPERATION_NOT_SUPPORTED) pure_words = pure_words if pure_words else "" #创建话题时进行敏感词过滤,以前的代码进行了数字的过滤,并只过滤了pure_words部分 本次沿用 filterword_by_custom(filter_type=FILTER_WORD_TYPE.TOPIC_CONTENT, content=pure_words) if not content.strip(): return gen(CODES.TOPIC_CONTENT_CAN_NOT_BE_EMPTY) user = get_user_from_context(ctx) if not user: return gen(CODES.LOGIN_REQUIRED) with transaction.atomic(): p = Problem() p.topic_type = topic_type_id p.user_id = user.id p.answer = content p.ask = title p.is_mixed = is_mixed p.set_review_status(len(images)) if device_id: p.device_id = device_id p.save() for image_url in images: if image_url: t = TopicImage(topic_id=p.id, image_url=image_url) t.save() # 处理创建topic之后的数据放大 # 点赞灌水统一 http://wiki.wanmeizhensuo.com/pages/viewpage.action?pageId=27102037 # special_user = is_special_user(user_id=user.id) # fake_vote_v1(task_type=VOTE_TASK_TYPE.TOPIC, business_id=p.id, special_user=special_user) has_geo_tag = False tags = TagService.get_tags_by_tag_ids(tag_ids) for tag in tags: if tag.tag_type in (TAG_TYPE.PROVINCE, TAG_TYPE.CITY): has_geo_tag = True ProblemTag(problem=p, tag_id=tag.id).save() user_city_tagid = user.city_tag_id if not has_geo_tag and user_city_tagid: tag = TagService.get_tag_by_tag_id(user_city_tagid) if tag is None: logging_exception() else: ProblemTag(problem=p, tag_id=tag.id).save() tag_first = p.problemtag_set.first() if tag_first: tag_id = tag_first.tag_id tag = TagService.get_tag_by_tag_id(tag_id) data = {'tag_id': tag.id, 'name': tag.name} else: data = {} data['topic_id'] = p.id if draft_id: try: draft_id = int(draft_id) draft = Draft.objects.get(pk=draft_id) draft.delete() except: logging_exception() return data @bind('topic/delete') def mark_topic_as_delete(id): user = get_current_user() if not user: return gen(CODES.LOGIN_REQUIRED) try: problem = Problem.objects.get(pk=id) except Problem.DoesNotExist: return gen(CODES.TOPIC_NOT_FOUND) if user and problem.user_id == user.id: # 是自己发布的帖子 if problem.reply_num != 0: # 有评论不能自己删除 if problem.topic_type != TOPIC_TYPE.TOPIC: # 医生也只能没限制的删除讨论帖 return gen(CODES.ASK_SUOZHANG_TO_DELETE_TOPIC) if UserService.user_is_doctor(user.id): # 但是医生除外,2.2.0医生端可以任意删除自己的讨论帖 return gen(CODES.ASK_SUOZHANG_TO_DELETE_TOPIC) problem.flag = PROBLEM_FLAG_CHOICES.MARK_DELETED problem.is_online = False problem.is_public = False problem.last_modified = datetime.datetime.now() problem.save() # 帖子关联日记本时间更新 if problem.diary: problem.diary.last_modified = datetime.datetime.now() problem.diary.save() user_info = UserService.get_user_by_user_id(user.id) user_info.decr_topic_count() # 记录操作 DELETE_TOPIC if problem.diary is not None: problem.diary.set_diary_operate(DIARY_OPERATE.DELETE_TOPIC, problem.id) try: diary_extra = DiaryExtra.objects.get(diary=problem.diary) if diary_extra.topic_count: diary_extra.topic_count -= 1 diary_extra.save() except DiaryExtra.DoesNotExist: pass else: return gen(CODES.NO_PERMISSION) return gen(CODES.SUCCESS) @bind("topic/update_one") def topic_update_one(topic_id, content=None, images=[]): """话题更新 topic_ids: 话题ID, 必选 content: 回复的内容, is_online: 下线 """ user = get_current_user() if not user: return gen(CODES.LOGIN_REQUIRED) topic = Problem.get_by_id(topic_id) if not topic or topic.user_id != user.id: gen(CODES.TOPIC_NOT_FOUND) if not topic.is_online: return gen(CODES.TOPIC_HAS_BEEN_DELETED) if content: topic.answer = content if images is None: images = [] if len(images) > 0: with transaction.atomic(): TopicImage.objects.filter(topic=topic).delete() # 删掉老帖子 for image in images: if not all(i in image for i in ('image', 'type')): continue TopicImage.objects.create( topic=topic, image_url=image['image'], image_type=image['type'] ) # 添加新帖子 topic.set_review_status(len(images)) topic.last_modified = datetime.datetime.now() topic.save() # 帖子关联日记本时间更新 if topic.diary: topic.diary.last_modified = datetime.datetime.now() topic.diary.save() # 记录操作 UPDATE_TOPIC if topic.diary is not None: topic.diary.set_diary_operate(DIARY_OPERATE.UPDATE_TOPIC, topic.id) @bind("topic/get") def get_topic(topic_id): try: topic = Problem.objects.get(pk=topic_id) if not (topic.is_online and topic.flag == PROBLEM_FLAG_CHOICES.NORMAL): return gen(CODES.TOPIC_NOT_FOUND) return topic.data() except Problem.DoesNotExist: return gen(CODES.TOPIC_NOT_FOUND) @bind('topic/get_topic') def get_topic_info(topic_id): """ NOTE: DESC: 获取帖子信息 :param topic_id: int :return: version: v5.7 """ try: topic = Problem.get_by_id(topic_id) if topic is None or not topic.visible: return gen(CODES.TOPIC_NOT_FOUND) image = topic.images.first() if hasattr(topic, 'images') else None image_url = image.image_url if image else '' user = UserService.get_user_by_user_id(topic.user_id) result = { 'id': topic.id, 'title': topic.title, 'reply_num': topic.reply_num, 'user_id': topic.user_id, 'nick_name': user.nickname, 'portrait': user.portrait, 'content': topic.answer, 'membership_level': user.membership_level, 'doctor_id': '', 'hospital_id': '', 'image_url': image_url, 'diary_id': topic.diary_id, } user_bz = UserService.get_doctor_hospital_id_by_user_id(topic.user_id) if user_bz: result['user_id'] = user_bz['user_id'] result['doctor_id'] = user_bz['doctor_id'] result['hospital_id'] = user_bz['hospital_id'] result['share_data'] = topic.get_topic_share_data_from_db() return result except Problem.DoesNotExist: return gen(CODES.TOPIC_NOT_FOUND) @bind('topic/get_doctor_topics') def get_doctor_topics(doctor_id=None, start_num=0, count=10, subtype=0): """医生参与的话题 subtype: 0 表示查询结果集按 id 排序,否则按 like_num 和 id 排序 """ replies_ids = TopicReply.objects.filter(doctor_id=doctor_id, problem__isnull=False, problem__is_online=True, is_online=True).values_list('id', flat=True) # 排序(按照id的降序排列),分页 if subtype == "0": replies_ids = replies_ids.order_by("-id")[start_num:start_num + count] else: # 按照like_num降序排列,like_num相同的按照id降序排列 replies_ids = replies_ids.order_by("-like_num", "-id")[start_num:start_num + count] replies = TopicReply.objects.filter(id__in=list(replies_ids)) return [reply.get_reply_info_for_doctor() for reply in replies] @bind('topic/get_topic_num_by_ids') def topic_vote_amount(ids): """ :param ids: topic_id list :return: [ { 'id': 1, 'data': { 'vote_amount': int, 'view_amount': int, } } ] """ if len(ids) == 0: return [] topics = Problem.objects.filter(id__in=ids) result = [] for topic in topics: data = { 'vote_amount': topic.vote_amount, 'view_amount': Problem.get_view_amount_of_id(topic.id), } d = { 'id': topic.id, 'data': data, } result.append(d) return result @bind('topic/get_topic_info_by_ids') def topic_info(ids, user_id=None): """ :param ids: topic_id list user_id :return: [ { 'id': 1, 'data': topic_info 一个大字典 } ] """ if len(ids) == 0: return [] user = User(user_id, None, None, None) topics = Problem.objects.filter(id__in=ids) result = [] for topic in topics: d = { 'id': topic.id, 'data': topic.get_topic_info(user), } result.append(d) return result @bind('topic/get_topic_info_by_user_ids') def topic_info_by_user_ids(user_ids, viewer_user_id=None, topic_id_only=False): """ :param user_ids: :param topic_id_only: :return: [{ "id": 1, "user_id": 2, "data": topic_info }, ...] """ if len(user_ids) == 0: return [] topics = Problem.objects.filter(user_id__in=user_ids, is_online=True) result = [] for topic in topics: d = {'id': topic.id, 'user_id': topic.user_id} if not topic_id_only: d['data'] = topic.get_topic_info(viewer_user_id) result.append(d) return result @bind('topic/get_tags_by_topic_ids') def topic_get_tags(ids): """ :param ids: topic_id list :return: [ { 'id': 1, 'data': [{ 'name': str, 'tag_id': int, }, ...] } ] """ if len(ids) == 0: return [] topic_tags = ProblemTag.objects.filter(problem_id__in=ids) topic_tag_dict = defaultdict(lambda: []) tags = [] for tt in topic_tags: topic_tag_dict[tt.problem_id].append(tt.tag_id) tags.append(tt.tag_id) tag_objs = TagService.get_tags_by_tag_ids(list(set(tags))) tag_obj_dict = {t.id: t for t in tag_objs} result = [] for topic_id in topic_tag_dict: tag_ids = topic_tag_dict[topic_id] tag_data = [] for tag_id in tag_ids: tag_obj = tag_obj_dict.get(tag_id) if not tag_obj: continue tag_data.append({'tag_id': tag_obj.id, 'name': tag_obj.name}) d = { 'id': topic_id, 'data': tag_data, } result.append(d) return result @bind('topic/update_reply_num_by_ids') def topic_update_reply_num(ids, is_online=False): """ :param ids: id list, is_online :return: [ { 'id': 1, 'data': bool, } ] """ if len(ids) == 0: return [] topics = Problem.objects.filter(id__in=ids) result = [] for topic in topics: d = { 'id': topic.id, 'data': topic.update_reply_num(is_online), } result.append(d) return result @bind_context('topic/check') def topic_check(ctx, start_num, count, reply_id): result = {} user = get_user_from_context(ctx) try: reply = TopicReply.objects.get(pk=reply_id) try: if reply.problem.diary: # 如果是某一个日记本下的回复的话,则跳转到兼容的方法处理 return TopicReply.reply_detail_diary(user=user, reply_id=reply_id, start_num=start_num, count=count) except: pass detail = reply.get_reply_detail(user.id) result.update(detail) result['reply_id'] = detail['reply']['reply_id'] result['content'] = detail['topic']['problem_answer'] result['user_portrait'] = detail['user']['portrait'] result['user_id'] = detail['user']['user_id'] result['doctor_id'] = detail['user']['doctor_id'] result['user_nickname'] = detail['user']['user_name'] result['doctor_name'] = detail['user']['doctor_name'] result['favor_amount'] = detail['reply']['favor_amount'] result['reply_date'] = detail['reply']['reply_date'] result['comments'] = [] result['topic_id'] = reply.problem.id result['title'] = reply.problem.get_title() result['comment_count'] = reply.comments.count() doctor_reply_sql = """ (case when api_topicreply.doctor_id !='' then 1 else 0 end) """ comments = reply.comments.extra( select={'doctor_reply': doctor_reply_sql}) comments = comments.extra(order_by=['-doctor_reply', 'id']) comments = comments[start_num: start_num + count] _comments_ids = [c.id for c in comments] result['comments'] = comment_data_by_reply_ids(_comments_ids) return result except TopicReply.DoesNotExist: result = {'error': 1, 'message': u'不存在'} result['notexists'] = True return result @bind('topic/create/check') def create_check(content="", is_new_version=False): user = get_current_user() if not user: return gen(CODES.LOGIN_REQUIRED) dd = datetime.datetime.now() - datetime.timedelta(seconds=30) user_problems = Problem.objects.filter(user_id=user.id, created_time__gt=dd) if user_problems and not is_new_version: return {'error': 1, 'message': u'为避免恶意发布,请间隔30s后再发'} # TODO 把msg迁移至下游服务? 目前来说是没时间精力... if not DoctorService.doctor_can_create_topic(user_id=user.id): return {'error': 1, 'message': u'抱歉,您本周发帖次数已达上限'} # 对日记贴更新进行敏感词过滤,这里是对帖子部分进行过滤,以前的代码进行了数字的过滤,本次沿用 content = content if content else "" filterword_by_custom(filter_type=FILTER_WORD_TYPE.TOPIC_CONTENT, content=content) return {"error": 0} @bind_context('topic/pre_create_topic') def topic_pre_info(ctx, diary_id): user = get_user_from_context(ctx) if not user: return gen(CODES.LOGIN_REQUIRED) try: diary = Diary.objects.get(pk=diary_id, user_id=user.id) except: return gen(CODES.DIARY_NOT_FOUND) if diary.operation_time: days = (datetime.datetime.now() - diary.operation_time).days # v7.6.25 不管时间如何 都必须返回获取到的时间,当天的零点时间戳 整数类型 timestamp = int(time.mktime(diary.operation_time.replace(hour=0, minute=0, second=0).timetuple())) else: days = 0 timestamp = None # 避免取不到的情况 直接制空 diary_tags = diary.tags if diary_tags: tags = [ {'tag_id': tag.id, 'name': tag.name} for tag in diary_tags if tag.tag_type in [ TAG_TYPE.BODY_PART, TAG_TYPE.BODY_PART_SUB_ITEM, TAG_TYPE.ITEM_WIKI ] ] else: tags = [] # change 09-10, 对用户服务上传视频不做限制 can_create_video = user.can_create_video topic_nums = diary.topic_num return { 'days': days, 'timestamp': timestamp, 'tags': tags, 'can_create_video': can_create_video, 'topics_num': topic_nums, } @bind("topic/sink") def topic_sinking(topic_id): """ 话题下沉 """ user = get_current_user() if not user: return gen(CODES.LOGIN_REQUIRED) try: topic = Problem.objects.get(id=topic_id) topic_score, created = TopicScore.objects.get_or_create(topic_id=topic.id, user_id=user.id) topic_score.score = 0 topic_score.save() except: return { 'updated': False, } return { 'updated': True, } @bind('topic/info_for_doctor_notification') def info_for_notification(topic_id): if topic_id is None: return None try: topic = Problem.objects.get(id=topic_id) except Problem.DoesNotExist: return gen(CODES.TOPIC_NOT_FOUND) topic_info = { 'id': topic.id, 'content': escape(topic.answer), } image = topic.images.first() if image: topic_info['image'] = image.image_url else: u = UserService.get_user_by_user_id(topic.user_id) topic_info['image'] = u.portrait diary = topic.diary info = { 'id': diary.id, 'diary_title': diary.title, 'service_id': diary.service_id, 'topic': topic_info, } return info @bind('topic/get_user_topics_count') def get_user_topics_count(user_id): query = Q(is_online=True, user_id=user_id) & (Q(topic_type=TOPIC_TYPE.ACTIVITY)) return Problem.objects.filter(query).count() @bind_context('topic/get_index_banner_topic_by_ids') def get_index_banner_topic_by_ids(ctx, topic_ids): user = get_user_from_context(ctx) _info = topic_list_manager.generate_info_for_index_banner_by_ids(topic_ids, user.id) return _info @bind('topic/check_topic') def check_topic_for_multi_topic(topic_id): try: p = Problem.objects.get(id=topic_id) if p.user_id == 22: return False, [] if p.topic_type != TOPIC_TYPE.SHARE: return False, [] return True, p.get_tags() except Problem.DoesNotExist: return False, [] @bind('topic/get_topic_count_by_tag_ids') def get_topic_count_by_tag_ids(tag_ids, annotate=False): ''' 粗略计算标签下的日记帖数,之后如果要加精细的过滤条件, 可直接调整, 目前只用于非精确的统计展示 ''' if annotate: topic_count_list = ProblemTag.objects.filter(tag_id__in=tag_ids). \ values("tag_id").annotate(count=Count('problem_id')) return {str(item['tag_id']): item['count'] for item in topic_count_list} diary_topic_count = ProblemTag.objects. \ filter(tag_id__in=tag_ids). \ values_list('problem', flat=True).distinct().count() return {'diary_topic_count': diary_topic_count} @bind('topic/update_video_cover') def update_video_cover(topic_id, video_url, second=0): cover_url = UploadVideoPicture(url=video_url, second=second) video=Video.objects.get(topic_id=topic_id) video.video_pic=cover_url video.save() set_water_mark_to_video.delay(video.id) return {'cover_url': cover_url} @bind_context("topic/get_infos_by_ids") def get_topics_by_ids(ctx, topic_ids): """ 获取topic 用户主页相册专用 相册+内容 信息 其中,点赞数、评论数是日记本下的日记帖之和,收藏数是日记本的数据 目前用户主页相册使用该接口~713 """ user = get_user_from_context(ctx) if not topic_ids: return {} result, diary_ids, topic_classify, social_data = [], [], {}, {} topics = Problem.objects.filter(id__in=topic_ids, is_online=True) for item in topics: content_data = item.data() if item.diary_id: diary_ids.append(item.diary_id) if not topic_classify.get(item.diary_id): social_data[item.diary_id] = {} topic_classify[item.diary_id] = [item.id] else: topic_classify[item.diary_id].append(item.id) item_data = { "id": item.id, "diary_id": item.diary_id, "images": content_data['topic'].get("images", []), "content": content_data['topic'].get("content", []), "video": item.get_video_info(), "is_favored": item.is_favored_by(user.id) if user else False, "is_voted": item.is_voted(user.id) if user else False, "create_time": item.created_time.timestamp() } result.append(item_data) diaries = Diary.objects.filter(id__in=diary_ids) diaries_info = {} for diary in diaries: diaries_info[diary.id] = { "vote_num": diary.vote_num, "comment_count": diary.reply_num, } for diary_id, topic_ids in topic_classify.items(): topic_ids = Problem.objects.filter(diary_id=diary_id).values_list("id", flat=True) social_data[diary_id]['vote_count'] = diaries_info[diary_id].get("vote_num", 0) if diaries_info.get(diary_id) else 0 social_data[diary_id]['comment_count'] = diaries_info[diary_id].get("comment_count", 0) if diaries_info.get(diary_id) else 0 social_data[diary_id]['favor_count'] = ProblemFavor.objects.filter(problem_id__in=list(topic_ids), is_deleted=False).count() social_data[diary_id]['favor_count'] += DiaryFavor.objects.filter(diary_id=diary_id, is_deleted=False).count() for topic in result: if topic.get("diary_id") in social_data: topic.update(social_data.get(topic.get("diary_id"))) return result @bind('mimas/diary_audit/fake_vote') def diary_audit_fake_vote(diary_id, content_level): """ 新增的日记帖 审核等级大于等于良好时 触发点赞灌水 且只需要灌水一次,重复审核不再灌水 :param diary_id: :param content_level: :return: """ diary = Diary.objects.get(id=diary_id) audit_time = diary.audit_time if not audit_time: topics = Problem.objects.filter( diary_id=diary.id, is_online=True, ).order_by('-id').values('id', 'user_id') else: topics = Problem.objects.filter( diary_id=diary.id, is_online=True, created_time__gte=audit_time, ).order_by('-id').values('id', 'user_id') if not topics: return for topic in topics[:5]: special_user = is_special_user(user_id=topic['user_id']) fake_vote_v1( task_type=VOTE_TASK_TYPE.TOPIC, business_id=topic['id'], special_user=special_user, content_level=content_level, ) @bind("topic/get_topic_rel_infos") def get_topic_rel_infos(topic_id): """ 获取帖子关联的数据 :param topic_id: :return: """ result = {} if not topic_id: return result routine = GroupRoutine() _map_list = [ ("v1_rel_tags_info", topic_list_manager.get_topic_tag_info_by_topic_ids), ("v3_rel_tags_info", topic_list_manager.get_topic_tagv3_info_by_topic_ids), ] for key, func in _map_list: routine.submit(key, func, [topic_id]) routine.go() for key, _ in _map_list: _data = routine.results.get(key, {}) result.update({ key: _data.get(topic_id, []) }) return result