def posts(request, payload={}, short_id=None): """ Posts endpoint of the example.com public api Request with an id parameter: /public_api/posts/1qkx8 POST JSON in the following format: POST /public_api/posts/ {"ids":["1qkx8","ma6fz"]} """ Metrics.api_comment.record(request) ids = payload.get('ids') if short_id and not ids: try: comment = Comment.details_by_id(long_id(short_id), promoter=PublicAPICommentDetails) (comment,) = CachedCall.multicall([comment]) return comment.to_client() except (ObjectDoesNotExist, util.Base36DecodeException): raise ServiceError("Post not found") elif ids: ids = [long_id(x) for x in set(ids)] calls = [Comment.details_by_id(id, ignore_not_found=True, promoter=PublicAPICommentDetails) for id in ids] comments = CachedCall.multicall(calls, skip_decorator=True) return {'posts': [x.to_client() for x in comments if x]}
def create_comment(**kwargs): kwargs['author'] = kwargs.get('author', create_user()) kwargs['timestamp'] = kwargs.get('timestamp', Services.time.time()) kwargs['anonymous'] = kwargs.get('anonymous', False) if kwargs.get('parent_comment') is None: kwargs['title'] = kwargs.get('title', 'Sample title.') comment = Comment(**kwargs) comment.save() return comment
def create_comment(**kwargs): kwargs["author"] = kwargs.get("author", create_user()) kwargs["timestamp"] = kwargs.get("timestamp", Services.time.time()) kwargs["anonymous"] = kwargs.get("anonymous", False) if kwargs.get("parent_comment") is None: kwargs["title"] = kwargs.get("title", "Sample title.") comment = Comment(**kwargs) comment.save() return comment
def _get_hot_slice_by_threads(rlbb, nav_slice): ops_and_scores = rlbb.with_scores[nav_slice] if rlbb else [] ops = [Comment(id=id) for (id, score) in ops_and_scores] max_from_thread = 3 # Got the OPs, now I need to bulk fetch the top replies. pipeline = redis.pipeline() for comment in ops: pipeline.get(comment.redis_score.key) for comment in ops: pipeline.zrevrange(comment.popular_replies.key, 0, max_from_thread - 1, withscores=True) results = pipeline.execute() op_scores, pop_reply_lists = results[:len(ops)], results[len(ops):] ids = [] if ops_and_scores: # Lowest score sets the threshold, but replies get a "boost" factor cutoff = ops_and_scores[-1][1] / Config.get('reply_boost', 1) for op, op_score, pop_replies in zip(ops, op_scores, pop_reply_lists): items = [(int(id), float(score or 0)) for (id,score) in [(op.id, op_score)] + pop_replies] items.sort(key=lambda (id, score): -score) ids += [id for (id, score) in items if score >= cutoff][:max_from_thread] return ids
def post_quest_idea(request, title, content, name, email): user = User.objects.get(username=settings.QUEST_IDEAS_USERNAME) text = "Name: {}\n\nEmail: {}".format(name, email) replied_comment, parent_comment, reply_content, external_content, category, title = validate_and_clean_comment( user, reply_text=text, reply_content=content, title=title, ) comment = Comment.create_and_post( request, user, False, category, reply_content, parent_comment=parent_comment, reply_text=text, replied_comment=replied_comment, external_content=external_content, fact_metadata={}, title=title, ) return {'comment': comment.details()}
def api_monster_details(request, short_id, payload={}): view_data = CommentViewData(request, short_id) main_comment = view_data.op_comment replies = [Comment.details_by_id(cid) for cid in view_data.reply_ids] has_replies = len(replies) > 0 ( (main_comment,), replies ) = CachedCall.many_multicall( [main_comment], replies, ) treplies = [] made_bottom = False for reply in replies: cur = reply.to_client() if reply.real_author == request.user.username: cur['current_user_authored'] = made_bottom = True treplies.append(cur) ctx = { 'top': main_comment, 'bottoms': treplies, 'current_user_made_bottom': made_bottom, 'current_user_made_top': main_comment.real_author == request.user.username, 'start_content': Content.all_objects.get(id=Content.SMALL_DRAW_FROM_SCRATCH_PK).details(), } return ctx
def from_ids(cls, comment_ids): """ Returns a list of CommentDetails instances. Does not include user pins. """ from canvas.models import Comment details = [ Comment.details_by_id(comment_id) for comment_id in comment_ids ] return CachedCall.many_multicall(details)[0]
def brief_replies(self): ids = [x for x in self.replies.values_list('id', flat=True)] calls = [ Comment.details_by_id(id, promoter=BriefPublicAPICommentDetails) for id in ids ] return CachedCall.multicall(calls, skip_decorator=True)
def news_img(url): token = os.path.basename(urlparse.urlparse(url).path) if "reply" in url: post_id = int(token) else: post_id = util.base36decode_or_404(token) img_url = Comment.details_by_id(post_id)()['reply_content']['thumbnail']['name'] return "<a href='%s'><img src='http://example.com/ugc/%s'></a>" % (url, img_url)
def from_queryset_with_viewer_stickers(cls, viewer, comments): bottoms, tops = CachedCall.many_multicall([cmt.details for cmt in comments], [cmt.thread.op.details for cmt in comments]) tiles = [] for bottom, top in zip(bottoms, tops): tile = cls(bottom, top) tile.viewer_sticker = Comment.get_sticker_from_user_for_comment_id(bottom.id, viewer) tiles.append(tile) return tiles
def from_queryset_with_viewer_stickers(cls, viewer, comments): bottoms, tops = CachedCall.many_multicall( [cmt.details for cmt in comments], [cmt.thread.op.details for cmt in comments]) tiles = [] for bottom, top in zip(bottoms, tops): tile = cls(bottom, top) tile.viewer_sticker = Comment.get_sticker_from_user_for_comment_id( bottom.id, viewer) tiles.append(tile) return tiles
def news_img(url): token = os.path.basename(urlparse.urlparse(url).path) if "reply" in url: post_id = int(token) else: post_id = util.base36decode_or_404(token) img_url = Comment.details_by_id( post_id)()['reply_content']['thumbnail']['name'] return "<a href='%s'><img src='http://example.com/ugc/%s'></a>" % (url, img_url)
def from_shared_op_details_with_viewer_stickers(cls, viewer, top_details, reply_details): """ `top_details` is a single CommentDetails instance of the monster top. `reply_details` is a list of CommentDetails instances, of the monster replies/bottoms. """ tiles = [] for bottom in reply_details: tile = cls(bottom, top_details) tile.viewer_sticker = Comment.get_sticker_from_user_for_comment_id(bottom.id, viewer) tiles.append(tile) return tiles
def get_img_url(url, image_size="thumbnail"): token = os.path.basename(urlparse.urlparse(url).path) if "reply" in url: post_id = int(token) else: post_id = util.base36decode_or_404(token) try: img_url = Comment.details_by_id(post_id)().reply_content[image_size]['name'] except (KeyError, Comment.DoesNotExist): img_url = "" return img_url
def is_pinned(self, viewer): """ `viewer` is the user who will see these comments. It *must* come from a request object, in order for `is_authenticated` to make sense. If they're logged out, they won't see pins. """ if not viewer.is_authenticated(): return False if self.pins is None: self.pins = Comment.pins_by_id(self._comment_details.thread_op_comment_id)() return viewer.id in self.pins
def from_shared_op_details_with_viewer_stickers(cls, viewer, top_details, reply_details): """ `top_details` is a single CommentDetails instance of the monster top. `reply_details` is a list of CommentDetails instances, of the monster replies/bottoms. """ tiles = [] for bottom in reply_details: tile = cls(bottom, top_details) tile.viewer_sticker = Comment.get_sticker_from_user_for_comment_id( bottom.id, viewer) tiles.append(tile) return tiles
def suggested_users(request): user_list = [] users = sample(SUGGESTED_USERS, 5) users = list(User.objects.filter(username__in=users, is_active=True)) for user in users: if user.userinfo.profile_image is not None: avatar_comment = Comment.details_by_id( user.userinfo.profile_image.id)() else: avatar_comment = None is_following = False try: is_following = request.user.is_following(user) except AttributeError: pass user_list.append({ 'user': user, 'avatar_comment': avatar_comment, 'is_following': is_following, 'is_self': request.user == user, }) topics = sample(SUGGESTED_TOPICS, 5) topics = [{'name': topic} for topic in topics] topic_previews = Content.all_objects.filter( id__in=SUGGESTED_TOPIC_PREVIEWS.values()) topic_previews = CachedCall.multicall( [preview.details for preview in topic_previews]) preview_mapping = dict([(content['id'], content) for content in topic_previews]) try: followed_tags = request.user.redis.followed_tags except AttributeError: followed_tags = [] for topic in topics: topic['preview'] = preview_mapping.get( SUGGESTED_TOPIC_PREVIEWS[topic['name']]) topic['is_following'] = topic['name'] in followed_tags ctx = { 'request': request, 'users': user_list, 'topics': topics, } return r2r_jinja('onboarding/suggested_users.html', ctx, request)
def post_comment(request, user, post_data, persist_url=True): reply_text = post_data.get('reply_text', '') try: replied_comment, parent_comment, reply_content, external_content, category, title = ( validate_and_clean_comment( user, reply_text=reply_text, parent_comment=post_data.get('parent_comment'), replied_comment=post_data.get('replied_comment'), reply_content=post_data.get('reply_content'), category=post_data.get('category'), external_content=post_data.get('external_content'), title=post_data.get('title'), )) post_anon = True if category and category == MONSTER_GROUP: post_anon = False comment = Comment.create_and_post( request, user, post_anon, # Anonymous. category, reply_content, parent_comment=parent_comment, reply_text=reply_text, replied_comment=replied_comment, external_content=external_content, title=title, ) post_pending_url = comment.details().url if persist_url: if category and category.name == MONSTER_GROUP: post_pending_url = '/monster/{0}'.format( base36encode(comment.thread.op.id)) user.kv.post_pending_signup_url.set(post_pending_url) return comment except ServiceError, e: # Silently drop the post if an error occurs. # We tried validating it prior to posting, but something went wrong between then and now # and it no longer validates. Should be rare. Metrics.logged_out_reply_dropped.record(request, extra_info=extra_info, service_error=e)
def news_img(url): token = os.path.basename(urlparse.urlparse(url).path) if "reply" in url: post_id = int(token) else: post_id = util.base36decode_or_404(token) try: comment_details = Comment.details_by_id(post_id)() img_url = comment_details.reply_content['thumbnail']['name'] url = comment_details.url except (KeyError, Comment.DoesNotExist): img_url = "" return "<a href='%s'><img class='content' src='%s'></a>" % (url, img_url)
def suggested_users(request): user_list = [] users = sample(SUGGESTED_USERS, 5) users = list(User.objects.filter(username__in=users, is_active=True)) for user in users: if user.userinfo.profile_image is not None: avatar_comment = Comment.details_by_id(user.userinfo.profile_image.id)() else: avatar_comment = None is_following = False try: is_following = request.user.is_following(user) except AttributeError: pass user_list.append({ 'user' : user, 'avatar_comment' : avatar_comment, 'is_following' : is_following, 'is_self' : request.user == user, }) topics = sample(SUGGESTED_TOPICS, 5) topics = [{'name': topic} for topic in topics] topic_previews = Content.all_objects.filter(id__in=SUGGESTED_TOPIC_PREVIEWS.values()) topic_previews = CachedCall.multicall([preview.details for preview in topic_previews]) preview_mapping = dict([(content['id'], content) for content in topic_previews]) try: followed_tags = request.user.redis.followed_tags except AttributeError: followed_tags = [] for topic in topics: topic['preview'] = preview_mapping.get(SUGGESTED_TOPIC_PREVIEWS[topic['name']]) topic['is_following'] = topic['name'] in followed_tags ctx = { 'request': request, 'users': user_list, 'topics': topics, } return r2r_jinja('onboarding/suggested_users.html', ctx, request)
def post_comment(request, user, post_data, persist_url=True): reply_text = post_data.get("reply_text", "") try: replied_comment, parent_comment, reply_content, external_content, category, title = validate_and_clean_comment( user, reply_text=reply_text, parent_comment=post_data.get("parent_comment"), replied_comment=post_data.get("replied_comment"), reply_content=post_data.get("reply_content"), category=post_data.get("category"), external_content=post_data.get("external_content"), title=post_data.get("title"), ) post_anon = True if category and category == MONSTER_GROUP: post_anon = False comment = Comment.create_and_post( request, user, post_anon, # Anonymous. category, reply_content, parent_comment=parent_comment, reply_text=reply_text, replied_comment=replied_comment, external_content=external_content, title=title, ) post_pending_url = comment.details().url if persist_url: if category and category.name == MONSTER_GROUP: post_pending_url = "/monster/{0}".format(base36encode(comment.thread.op.id)) user.kv.post_pending_signup_url.set(post_pending_url) return comment except ServiceError, e: # Silently drop the post if an error occurs. # We tried validating it prior to posting, but something went wrong between then and now # and it no longer validates. Should be rare. Metrics.logged_out_reply_dropped.record(request, extra_info=extra_info, service_error=e)
def get_info(user): comment_id = user.kv.last_sticker_comment_id.get() if comment_id: from canvas.models import Comment details = Comment.details_by_id(comment_id)() url = details.url else: url = None level = user.kv.sticker_level.get() schedule = economy.sticker_schedule(level) return { 'type_id': user.kv.last_sticker_type_id.get(), 'timestamp': user.kv.last_sticker_timestamp.get(), 'comment_id': comment_id, 'url': url, 'level': level, 'level_progress': user.kv.sticker_inbox.get(), 'level_total': schedule, }
def thread_context(self): top_reply_ids = self.top_reply_ids(force_show=features.thread_new(self.request)) ctx = { 'short_id': self.short_id, 'page': self.page, 'gotoreply': self.gotoreply, 'viewer_is_staff': self.request.user.is_authenticated() and self.request.user.is_staff, 'viewer_is_thread_author': self.is_author, 'root': '/p/', 'op_content': self.op_content, 'op_category': self.op_category, 'page': self.page, 'per_page': self.per_page, 'num_replies': self.num_replies, 'reply_ids': self.reply_ids, 'pages': self.pages, 'page_reply_ids': self.page_reply_ids, 'page_current': self.page_current, 'page_next': self.page_next, 'page_last': self.page_last, 'page_penultimate': self.page_penultimate, 'explicit_page_view': self.explicit_page_view, # Recent replies. 'recent_reply_ids': self.recent_reply_ids, 'top_reply_ids': top_reply_ids, } # Get all the replies in one query, then create the appropriate lists. _all_replies = Comment.visible.in_bulk(self.page_reply_ids + self.recent_reply_ids + top_reply_ids) _recent_replies = [_all_replies[cid] for cid in self.recent_reply_ids] _top_replies = filter(bool, [_all_replies.get(cid) for cid in top_reply_ids]) _replies = [_all_replies[cid] for cid in self.page_reply_ids] replyable = [self._op_comment] + _replies + _recent_replies + _top_replies # Get all comment ids (ids so 0 queries) that any of these comments are replies to, that aren't in this # page, so we can render them on hover. replied_ids = ([reply.replied_comment_id for reply in replyable if (reply.replied_comment_id and reply.replied_comment_id not in [r.id for r in replyable])]) ctx.update({ 'replied_ids': replied_ids, 'replyable': replyable, }) recent_replies = [reply.details for reply in _recent_replies] replies = [Comment.details_by_id(reply.id) for reply in _replies] replied = [Comment.details_by_id(cid) for cid in replied_ids] top_replies = [reply.details for reply in _top_replies] if self.request.user.is_authenticated(): ctx['user_infos'] = {'pinned': self.request.user.id in self._op_comment.pins()} if self.request.user.is_staff: ctx['admin_infos'] = {self._op_comment.id: self._op_comment.admin_info} # For replies we only use the username, so grab those all in one query and put them in admin_infos. ctx['usernames'] = Comment.visible.filter(id__in=_all_replies.keys()).values('id', 'author__username') for reply_dict in ctx['usernames']: ctx['admin_infos'][reply_dict['id']] = {'username': reply_dict['author__username']} ctx['replies_channel'] = self._op_comment.replies_channel.sync() # Get relevant sidebar data remix_of, reply_to = [], [] # Remix of if self._op_content and self._op_content.remix_of: op_remix_of_caption = self._op_content.remix_of.first_caption if op_remix_of_caption: remix_of = [op_remix_of_caption.details] ctx['op_remix_of_caption'] = op_remix_of_caption # Reply to if self._op_comment.parent_comment and self._op_comment.parent_comment.is_visible(): reply_to = [self._op_comment.parent_comment.details] ( (op_comment,), (linked_comment,), remix_of, reply_to, replies, recent_replies, top_replies, replied, ) = CachedCall.many_multicall( [self.op_comment], [self.linked_comment], remix_of, reply_to, replies, recent_replies, top_replies, replied, ) op_comment.is_my_post = bool(self._op_comment.author == self.request.user) op_comment.moderated = op_comment.visibility not in Visibility.public_choices linked_comment = TemplateComment(linked_comment, is_op=(linked_comment.id == op_comment.id), request=self.request, title=op_comment.title) if self.page_current == 1 and op_comment.has_content(): first_comment_with_content = op_comment else: first_comment_with_content = None for reply in replies: if reply.has_content(): first_comment_with_content = reply break last_comment_with_content = None for reply in reversed(replies): if reply.has_content(): last_comment_with_content = reply break comment_to_expand = first_comment_with_content if self.gotoreply: comment_to_expand = linked_comment ctx.update({ 'op_comment': op_comment, 'linked_comment': linked_comment, 'remix_of': remix_of, 'reply_to': reply_to, 'replies': replies, 'recent_replies': recent_replies, 'top_replies': top_replies, 'top_remixes': [reply for reply in top_replies if reply.has_content()], 'replied': replied, 'linked_comment': linked_comment, 'large_thread_view': len(replies) >= 50, 'title': getattr(op_comment, 'title', None), 'first_comment_with_content': first_comment_with_content, 'last_comment_with_content': last_comment_with_content, 'comment_to_expand': comment_to_expand, }) return ctx
def brief_replies(self): ids = [x for x in self.replies.values_list('id', flat=True)] calls = [Comment.details_by_id(id, promoter=BriefPublicAPICommentDetails) for id in ids] return CachedCall.multicall(calls, skip_decorator=True)
def view(request, short_id, option=None): from apps.monster.jinja_tags import monster_image_tile view_data = CommentViewData(request, short_id) main_comment = view_data.op_comment replies = [Comment.details_by_id(cid) for cid in view_data.reply_ids] has_replies = len(replies) > 0 complete_link = option and (option == 'complete') if complete_link and request.user.is_anonymous(): fact.record('monster_start_flow', request, {'monster_id': short_id}) reply_id = None if option: try: reply_id = int(option) except ValueError: pass ( (main_comment,), replies ) = CachedCall.many_multicall( [main_comment], replies, ) replies = [reply for reply in replies if not reply.is_collapsed] monster_part = MonsterPart.get_by_comment(main_comment) main_comment_details = main_comment main_comment = TileDetails(main_comment) made_bottom = False made_top = main_comment.comment.real_author == request.user.username linked_monster_footer_image = "" current_monster_index = 0 for i in range(len(replies)): reply = replies[i] if reply_id is not None and reply.id == int(reply_id): current_monster_index = i elif reply.real_author == request.user.username and reply_id is None: current_monster_index = i made_bottom = True try: if (has_replies and replies[current_monster_index].reply_content and replies[current_monster_index].reply_content.footer): linked_monster_footer_image = replies[current_monster_index].reply_content.footer['name'] except (AttributeError, IndexError): pass made_part = made_top or made_bottom if made_part: CompletedMonsterSet(request.user).sadd(main_comment.comment.id) can_make_bottom = (not made_part) and complete_link can_invite = made_top # incomplete monster without an invite link, send to monster index if not has_replies and not complete_link and not can_invite: return HttpResponseRedirect('/monster') ctx = { 'can_invite': can_invite, 'can_make_bottom': can_make_bottom, 'current_monster_index': current_monster_index, 'domain': settings.DOMAIN, 'made_bottom': made_bottom, 'made_part': made_part, 'made_top': made_top, 'main_comment': main_comment, 'monster_content': main_comment.comment.reply_content, 'og_image_url': linked_monster_footer_image.replace("https", "http", 1), 'monster_group': MONSTER_GROUP, 'monster_name': main_comment.comment.title, 'replies': MonsterTileDetails.from_shared_op_details_with_viewer_stickers(request.user, main_comment_details, replies), 'request': request, 'short_id': main_comment.comment.short_id(), 'start_content': Content.all_objects.get(id=Content.SMALL_DRAW_FROM_SCRATCH_PK).details(), } return r2r_jinja('monster/view.html', ctx)
def from_id(cls, comment_id): """ Does not include user pins. """ from canvas.models import Comment return Comment.details_by_id(comment_id)()
def from_ids(cls, comment_ids): """ Returns a list of CommentDetails instances. Does not include user pins. """ from canvas.models import Comment details = [Comment.details_by_id(comment_id) for comment_id in comment_ids] return CachedCall.many_multicall(details)[0]
def from_comment_id(cls, comment_id): details = Comment.details_by_id(comment_id)() return cls(details)