def user_create_messages(sender, instance, created, *args, **kwargs): user = instance if created: authors = User.objects.filter(is_admin=True) or [user] author = authors[0] title = "Welcome!" content = html.render(name=NEW_USER_WELCOME_TEMPLATE, user=user) body = MessageBody.objects.create(author=author, subject=title, text=content, sent_at=now()) message = Message(user=user, body=body, sent_at=body.sent_at) message.save()
def award_create_messages(sender, instance, created, *args, **kwargs): "The actions to undertake when creating a new post" award = instance if created: # The user sending the notifications. user = award.user # Generate the message from the template. content = html.render(name=AWARD_CREATED_HTML_TEMPLATE, award=award, user=user) subject = "Congratulations: you won %s" % award.badge.name # Create the message body. body = MessageBody.objects.create(author=user, subject=subject, text=content) message = Message.objects.create(user=user, body=body, sent_at=body.sent_at)
def post_create_messages(sender, instance, created, *args, **kwargs): "The actions to undertake when creating a new post" from askcomrade.apps.users.models import User post = instance if created: # The user sending the notifications. author = instance.author # Insert email subscriptions to users that watch these posts if post.is_toplevel: cond1 = Q(profile__message_prefs=ALL_MESSAGES) cond2 = Q(profile__tags__name__in=post.parse_tags()) cond = cond1 | cond2 for watcher in User.objects.filter(cond).exclude(id=author.id): sub, flag = Subscription.objects.get_or_create( post=post, user=watcher, type=EMAIL_MESSAGE) # Get all subscriptions for the post. subs = Subscription.objects.get_subs(post).exclude(user=author) # Generate the message from the template. content = html.render(name=POST_CREATED_SHORT, post=post, user=author) # Generate the email message body. site = Site.objects.get_current() email_text = html.render(name=POST_CREATED_TEXT, post=post, user=author, site=site) # Generate the html message email_html = html.render(name=POST_CREATED_HTML, post=post, user=author, site=site) # Create the message body. body = MessageBody.objects.create(author=author, subject=post.root.title, text=content, sent_at=post.creation_date) # Collects the emails for bulk sending. emails, tokens = [], [] # This generator will produce the messages. def messages(): for sub in subs: message = Message(user=sub.user, body=body, sent_at=body.sent_at) # collect to a bulk email if the subscription is by email: if sub.type in (EMAIL_MESSAGE, ALL_MESSAGES): try: token = ReplyToken(user=sub.user, post=post, token=make_uuid(8), date=now()) from_email = settings.EMAIL_FROM_PATTERN % ( author.name, settings.DEFAULT_FROM_EMAIL) from_email = from_email.encode("utf-8") reply_to = settings.EMAIL_REPLY_PATTERN % token.token subject = settings.EMAIL_REPLY_SUBJECT % body.subject # create the email message email = mail.EmailMultiAlternatives( subject=subject, body=email_text, from_email=from_email, to=[sub.user.email], headers={'Reply-To': reply_to}, ) email.attach_alternative(email_html, "text/html") emails.append(email) tokens.append(token) except Exception as exc: # This here can crash the post submission hence the catchall logger.error(exc) yield message # Bulk insert of all messages. Bypasses the Django ORM! Message.objects.bulk_create(messages(), batch_size=100) ReplyToken.objects.bulk_create(tokens, batch_size=100) try: # Bulk sending email messages. conn = mail.get_connection() conn.send_messages(emails) except Exception as exc: logger.error("email error %s" % exc)
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 == (BUMP_POST) and post != post.root: messages.error(request, "Only top-level posts may be bumped!") return response if action == (BUMP_POST) and user.is_moderator: now = datetime.utcnow().replace(tzinfo=utc) Post.objects.filter(id=post.id).update(lastedit_date=now, lastedit_user=request.user) messages.success(request, "Post bumped") return response 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 render_digest(days, text_tmpl, html_tmpl, send, options, limit=10, verbosity=1): from_email = settings.DEFAULT_FROM_EMAIL from askcomrade.apps.posts.models import Post from askcomrade.apps.users.models import User site = site = Site.objects.get_current() start = (now() - timedelta(days=days)) # Posts created since the start date. top_posts = Post.objects.filter( status=Post.OPEN, type__in=Post.TOP_LEVEL, creation_date__gt=start).select_related('author') top_posts = top_posts.order_by('-view_count')[:limit] # Updated post created before the start date. upd_posts = Post.objects.filter(status=Post.OPEN, lastedit_date__gt=start) upd_posts = upd_posts.exclude( creation_date__gt=start, type__in=Post.TOP_LEVEL).select_related("author") upd_posts = upd_posts.order_by('-vote_count')[:limit] # Blog posts created since the start date. blogs = Post.objects.filter( status=Post.OPEN, type=Post.BLOG, creation_date__gt=start).select_related('author') blogs = blogs[:limit] # Total post count total_post_count = Post.objects.filter(status=Post.OPEN).count() # Total user count total_user_count = User.objects.filter().count() hard_worker = User.objects.filter(post__status=Post.OPEN, post__lastedit_date__gt=start) \ .annotate(total=Count("post")).order_by('-total').select_related("profile") hard_worker = hard_worker[:limit] params = dict( site=site, top_posts=top_posts, upd_posts=upd_posts, blogs=blogs, total_post_count=total_post_count, total_user_count=total_user_count, start=start.strftime("%b %d, %Y"), hard_worker=hard_worker, days=days, ) text_body = html_body = '' if text_tmpl: text_body = html.render(text_tmpl, **params) if html_tmpl: html_body = html.render(html_tmpl, **params) if verbosity > 0: extras = dict(digest_manage=reverse("digest_manage"), digest_unsubscribe=reverse("digest_unsubscribe", kwargs=dict(uuid=1))) print(text_body % extras) print(html_body % extras) if send: logger.info('sending emails') emails = map(string.strip, open(send)) def chunks(data, size): "Break into chunks of 100" for i in range(0, len(data), size): yield data[i:i + size] for chunk in chunks(emails, 100): users = User.objects.filter( email__in=chunk).select_related('profile') for user in users: try: extras = dict(digest_manage=reverse("digest_manage"), digest_unsubscribe=reverse( "digest_unsubscribe", kwargs=dict(uuid=user.profile.uuid))) text_content = text_body % extras html_content = html_body % extras subject = options['subject'] msg = EmailMultiAlternatives(subject, text_content, from_email, [user.email]) msg.attach_alternative(html_content, "text/html") msg.send() time.sleep(0.3) # Throttle on Amazon. except Exception as exc: logger.error('error %s sending email to %s' % (exc, user.email))