Beispiel #1
0
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()
Beispiel #2
0
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)
Beispiel #3
0
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)
Beispiel #4
0
    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
Beispiel #5
0
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))