def get_object(self): user = self.request.user obj = super(PostDetails, self).get_object() # Update the post views. Post.update_post_views(obj, request=self.request) # Adds the permissions obj = post_permissions(request=self.request, post=obj) # This will be piggybacked on the main object. obj.sub = Subscription.get_sub(post=obj, user=user) # Just a sanity check to start at top level. if obj != obj.root: obj = obj.root # Populate the object to build a tree that contains all posts in the thread. # Answers sorted before comments. thread = [post_permissions(request=self.request, post=post) for post in Post.objects.get_thread(obj)] # Do a little preprocessing. answers = [p for p in thread if p.type == Post.ANSWER] tree = OrderedDict() for post in thread: if post.type == Post.COMMENT: tree.setdefault(post.parent_id, []).append(post) store = {Vote.UP: set(), Vote.BOOKMARK: set()} if user.is_authenticated(): pids = [p.id for p in thread] votes = Vote.objects.filter(post_id__in=pids, author=user).values_list("post_id", "type") for post_id, vote_type in votes: store.setdefault(vote_type, set()).add(post_id) # Shortcuts to each storage. bookmarks = store[Vote.BOOKMARK] upvotes = store[Vote.UP] def decorate(post): post.has_bookmark = post.id in bookmarks post.has_upvote = post.id in upvotes # Add attributes by mutating the objects map(decorate, thread + [obj]) # Additional attributes used during rendering obj.tree = tree obj.answers = answers # Add the more like this field post = super(PostDetails, self).get_object() return obj
def get_object(self): obj = super(PostDetails, self).get_object() # Raise 404 if a deleted post is viewed by an anonymous user if (obj.status == Post.DELETED): raise Http404() # Adds the permissions obj = post_permissions(request=self.request, post=obj) # This will be piggybacked on the main object. obj.sub = Subscription.get_sub(post=obj) # Bail out if not at top level. if not obj.is_toplevel: return obj # Populate the object to build a tree that contains all posts in the thread. # Answers sorted before comments. thread = [post_permissions(request=self.request, post=post) for post in Post.objects.get_thread(obj)] # Do a little preprocessing. answers = [p for p in thread if p.type == Post.ANSWER and not p.is_fake_test_data] tree = OrderedDict() for post in thread: if post.type == Post.COMMENT: tree.setdefault(post.parent_id, []).append(post) store = {Vote.UP: set(), Vote.BOOKMARK: set()} pids = [p.id for p in thread] votes = Vote.objects.filter(post_id__in=pids).values_list("post_id", "type") for post_id, vote_type in votes: store.setdefault(vote_type, set()).add(post_id) # Shortcuts to each storage. bookmarks = store[Vote.BOOKMARK] upvotes = store[Vote.UP] # Can the current user accept answers can_accept = True def decorate(post): post.has_bookmark = post.id in bookmarks post.has_upvote = post.id in upvotes post.can_accept = can_accept or post.has_accepted # Add attributes by mutating the objects map(decorate, thread + [obj]) # Additional attributes used during rendering obj.tree = tree obj.answers = answers return obj
def get(self, request, *args, **kwargs): post = self.get_obj() post = post_permissions(request, post) if not post.is_editable: messages.warning(request, "You may not moderate this post") return HttpResponseRedirect(post.root.get_absolute_url()) form = self.form_class(pk=post.id) context = dict(form=form, post=post) return render(request, self.template_name, context)
def get_object(self): user = self.request.user obj = super(PostDetails, self).get_object() # Update the post views. Post.update_post_views(obj, request=self.request) # Adds the permissions obj = post_permissions(request=self.request, post=obj) # This will be piggybacked on the main object. obj.sub = Subscription.get_sub(post=obj, user=user) # Bail out if not at top level. if not obj.is_toplevel: return obj # Populate the object to build a tree that contains all posts in the thread. # Answers sorted before comments. thread = [ post_permissions(request=self.request, post=post) for post in Post.objects.get_thread(obj, user) ] # Do a little preprocessing. answers = [p for p in thread if p.type == Post.ANSWER] tree = OrderedDict() for post in thread: if post.type == Post.COMMENT: tree.setdefault(post.parent_id, []).append(post) store = {Vote.UP: set(), Vote.DOWN: set(), Vote.BOOKMARK: set()} if user.is_authenticated(): pids = [p.id for p in thread] votes = Vote.objects.filter(post_id__in=pids, author=user).values_list( "post_id", "type") for post_id, vote_type in votes: store.setdefault(vote_type, set()).add(post_id) # Shortcuts to each storage. bookmarks = store[Vote.BOOKMARK] upvotes = store[Vote.UP] downvotes = store[Vote.DOWN] # Can the current user accept answers can_accept = obj.author == user def decorate(post): post.has_bookmark = post.id in bookmarks post.has_upvote = post.id in upvotes post.has_downvote = post.id in downvotes post.can_accept = can_accept or post.has_accepted # Add attributes by mutating the objects map(decorate, thread + [obj]) # Additional attributes used during rendering obj.tree = tree obj.answers = answers # Add the more like this field post = super(PostDetails, self).get_object() return obj
def post(self, request, *args, **kwargs): user = request.user post = self.get_obj() post = post_permissions(request, post) # The default return url response = HttpResponseRedirect(post.root.get_absolute_url()) if not post.is_editable: messages.warning(request, "You may not moderate this post") return response # Initialize the form class. form = self.form_class(request.POST, pk=post.id) # Bail out on errors. if not form.is_valid(): messages.error(request, "%s" % form.errors) return response # A shortcut to the clean form data. get = form.cleaned_data.get # These will be used in updates, will bypasses signals. query = Post.objects.filter(pk=post.id) root = Post.objects.filter(pk=post.root_id) action = get('action') if action == (OPEN, TOGGLE_ACCEPT) and not user.is_moderator: messages.error(request, "Only a moderator may open or toggle a post") return response if action == TOGGLE_ACCEPT and post.type == Post.ANSWER: # Toggle post acceptance. post.has_accepted=not post.has_accepted post.save() has_accepted = Post.objects.filter(root=post.root, type=Post.ANSWER, has_accepted=True).count() root.update(has_accepted=has_accepted) return response if action == MOVE_TO_ANSWER and post.type == Post.COMMENT: # This is a valid action only for comments. messages.success(request, "Moved post to answer") query.update(type=Post.ANSWER, parent=post.root) root.update(reply_count=F("reply_count") + 1) return response if action == MOVE_TO_COMMENT and post.type == Post.ANSWER: # This is a valid action only for answers. messages.success(request, "Moved post to answer") query.update(type=Post.COMMENT, parent=post.root) root.update(reply_count=F("reply_count") - 1) return response # Some actions are valid on top level posts only. if action in (CLOSE_OFFTOPIC, DUPLICATE) and not post.is_toplevel: messages.warning(request, "You can only close or open a top level post") return response if action == OPEN: query.update(status=Post.OPEN) messages.success(request, "Opened post: %s" % post.title) return response if action in CLOSE_OFFTOPIC: query.update(status=Post.CLOSED) messages.success(request, "Closed post: %s" % post.title) content = html.render(name="messages/offtopic_posts.html", user=post.author, comment=get("comment"), post=post) comment = Post(content=content, type=Post.COMMENT, parent=post, author=user) comment.save() return response if action == CROSSPOST: content = html.render(name="messages/crossposted.html", user=post.author, comment=get("comment"), post=post) comment = Post(content=content, type=Post.COMMENT, parent=post, author=user) comment.save() return response if action == DUPLICATE: query.update(status=Post.CLOSED) posts = Post.objects.filter(id__in=get("dupe")) content = html.render(name="messages/duplicate_posts.html", user=post.author, comment=get("comment"), posts=posts) comment = Post(content=content, type=Post.COMMENT, parent=post, author=user) comment.save() return response if action == DELETE: # Delete marks a post deleted but does not remove it. # Remove means to delete the post from the database with no trace. # Posts with children or older than some value can only be deleted not removed # The children of a post. children = Post.objects.filter(parent_id=post.id).exclude(pk=post.id) # The condition where post can only be deleted. delete_only = children or post.age_in_days > 7 or post.vote_count > 1 or (post.author != user) if delete_only: # Deleted posts can be undeleted by re-opening them. query.update(status=Post.DELETED) messages.success(request, "Deleted post: %s" % post.title) response = HttpResponseRedirect(post.root.get_absolute_url()) else: # This will remove the post. Redirect depends on the level of the post. url = "/" if post.is_toplevel else post.parent.get_absolute_url() post.delete() messages.success(request, "Removed post: %s" % post.title) response = HttpResponseRedirect(url) # Recompute post reply count post.update_reply_count() return response # By this time all actions should have been performed messages.warning(request, "That seems to be an invalid action for that post. \ It is probably ok! Actions may be shown even when not valid.") return response
def get_object(self): user = self.request.user obj = super(PostDetails, self).get_object() # Raise 404 if a deleted post is viewed by an anonymous user if (obj.status == Post.DELETED) and not self.request.user.is_moderator: raise Http404() # Update the post views. Post.update_post_views(obj, request=self.request) # Adds the permissions obj = post_permissions(request=self.request, post=obj) # This will be piggybacked on the main object. obj.sub = Subscription.get_sub(post=obj, user=user) # Bail out if not at top level. if not obj.is_toplevel: return obj # Populate the object to build a tree that contains all posts in the thread. # Answers sorted before comments. thread = [post_permissions(request=self.request, post=post) for post in Post.objects.get_thread(obj, user)] # Do a little preprocessing. answers = [p for p in thread if p.type == Post.ANSWER] tree = OrderedDict() for post in thread: if post.type == Post.COMMENT: tree.setdefault(post.parent_id, []).append(post) store = {Vote.UP: set(), Vote.BOOKMARK: set()} if user.is_authenticated(): pids = [p.id for p in thread] votes = Vote.objects.filter(post_id__in=pids, author=user).values_list("post_id", "type") for post_id, vote_type in votes: store.setdefault(vote_type, set()).add(post_id) # Shortcuts to each storage. bookmarks = store[Vote.BOOKMARK] upvotes = store[Vote.UP] # Can the current user accept answers can_accept = obj.author == user def decorate(post): post.has_bookmark = post.id in bookmarks post.has_upvote = post.id in upvotes post.can_accept = can_accept or post.has_accepted # Add attributes by mutating the objects decorate(obj) for post in thread: decorate(post) # Additional attributes used during rendering obj.tree = tree obj.answers = answers # Add the more like this field post = super(PostDetails, self).get_object() return obj
def get_object(self): obj = super(PostDetails, self).get_object() # Raise 404 if a deleted post is viewed by an anonymous user if (obj.status == Post.DELETED): raise Http404() # Adds the permissions obj = post_permissions(request=self.request, post=obj) # This will be piggybacked on the main object. obj.sub = Subscription.get_sub(post=obj) # Awards bounty_sats = 0 awards = [] bounties = Bounty.objects.filter(post_id=obj, is_active=True, is_payed=False).order_by("created") for b in bounties: bounty_sats += b.amt awards += BountyAward.objects.filter(bounty=b) if bounty_sats == 0: bounty_sats = None obj.bounty_sats = bounty_sats awards_dict = {} first_active_bounty = bounties.first() claim_delta_days = int( settings.CLAIM_TIMEDELTA.total_seconds() / 60.0 / 60.0 / 24.0, ) if first_active_bounty: for award in awards: if not first_active_bounty.award_time: msg = "award {} exists yet award_time is not set on the Bounty!".format( award.id) logger.error(msg) raise Exception(msg) this_award_granted = None this_award_expansion_time = None this_award_expanded = None this_award_anticipated = None this_preliminary_award_time = None this_take_custody_url = reverse("take-custody", kwargs={"award_id": award.id}) if first_active_bounty.award_time <= timezone.now(): this_award_granted = True award_expansion_time = first_active_bounty.award_time + settings.CLAIM_TIMEDELTA this_award_expansion_time = award_expansion_time if award_expansion_time >= timezone.now(): this_award_expanded = False else: this_award_expanded = True else: this_award_anticipated = True this_preliminary_award_time = first_active_bounty.award_time awards_dict[award.post.id] = AwardView( take_custody_url=this_take_custody_url, award_granted=this_award_granted, award_expansion_time=this_award_expansion_time, award_expanded=this_award_expanded, award_anticipated=this_award_anticipated, preliminary_award_time=this_preliminary_award_time, claim_delta_days=claim_delta_days) obj.awards = awards_dict # Stop adding info if not at top level. if not obj.is_toplevel: return obj # Populate the object to build a tree that contains all posts in the thread. # Answers sorted before comments. thread = [ post_permissions(request=self.request, post=post) for post in Post.objects.get_thread(obj) ] # Do a little preprocessing. answers = [ p for p in thread if p.type == Post.ANSWER and not p.is_fake_test_data ] tree = OrderedDict() for post in thread: if post.type == Post.COMMENT: tree.setdefault(post.parent_id, []).append(post) store = {Vote.UP: set(), Vote.BOOKMARK: set()} pids = [p.id for p in thread] votes = Vote.objects.filter(post_id__in=pids).values_list( "post_id", "type") for post_id, vote_type in votes: store.setdefault(vote_type, set()).add(post_id) # Shortcuts to each storage. bookmarks = store[Vote.BOOKMARK] upvotes = store[Vote.UP] # Can the current user accept answers can_accept = True def decorate(post): post.has_bookmark = post.id in bookmarks post.has_upvote = post.id in upvotes post.can_accept = can_accept or post.has_accepted # Add attributes by mutating the objects map(decorate, thread + [obj]) # Additional attributes used during rendering obj.tree = tree obj.answers = answers return obj