-from datetime import datetime, timedelta
+import datetime
+from forum.models import *
+from django.db import models
+from forum.utils.mail import send_template_email
from django.core.management.base import NoArgsCommand
-from django.utils.translation import ugettext as _
-from django.template import loader, Context, Template
-from django.core.mail import EmailMultiAlternatives
-from django.utils import translation
-from django.conf import settings
-from forum import settings
from forum.settings.email import EMAIL_DIGEST_CONTROL
-from forum import actions
-from forum.models import KeyValue, Action, User, QuestionSubscription
-from forum.utils.mail import send_email
+from django.utils import translation
import logging
-class QuestionRecord:
- def __init__(self, question):
- self.question = question
- self.records = []
+SHOW_N_MORE_ACTIVE_NEW_MEMBERS = 5
+SUB_QUESTION_LIST_LENGTH = 5
+TRY_N_USER_TAGS = 5
- def log_activity(self, activity):
- self.records.append(activity)
- def get_activity_since(self, since):
- activity = [r for r in self.records if r.action_date > since]
- answers = [a for a in activity if a.action_type == "answer"]
- comments = [a for a in activity if a.activity_type == "comment"]
- accepted = [a for a in activity if a.activity_type == "accept_answer"]
+class DigestQuestionsIndex(object):
+ def __init__(self, from_date):
+ self.from_date = from_date
- if len(accepted):
- accepted = accepted[-1:][0]
- else:
- accepted = None
+ new_questions = Question.objects.filter_state(deleted=False).\
+ filter(added_at__gt=from_date).\
+ annotate(n_actions=models.Count('actions')).\
+ annotate(child_count=models.Count('all_children'))
- return {
- 'answers': answers,
- 'comments': comments,
- 'accepted': accepted,
- }
+ hotness = lambda q: 3*q.child_count + q.n_actions
+ for q in new_questions:
+ q.hotness=hotness(q)
-class Command(NoArgsCommand):
- def handle_noargs(self, **options):
+ self.questions = sorted(new_questions, lambda q1, q2: q2.hotness - q1.hotness)
+ self.count = len(self.questions)
+
+ def unseen_question(self, user, question):
try:
- translation.activate(settings.LANGUAGE_CODE)
+ subscription = QuestionSubscription.objects.get(question=q, user=user)
except:
- logging.error("Unable to set the locale in the send emails cron job")
-
- digest_control = EMAIL_DIGEST_CONTROL.value
-
- if digest_control is None:
- digest_control = KeyValue(key='DIGEST_CONTROL', value={
- 'LAST_DAILY': datetime.now() - timedelta(days=1),
- 'LAST_WEEKLY': datetime.now() - timedelta(days=1),
- })
-
- self.send_digest('daily', 'd', digest_control.value['LAST_DAILY'])
- digest_control.value['LAST_DAILY'] = datetime.now()
-
- if digest_control.value['LAST_WEEKLY'] + timedelta(days=7) <= datetime.now():
- self.send_digest('weekly', 'w', digest_control.value['LAST_WEEKLY'])
- digest_control.value['LAST_WEEKLY'] = datetime.now()
+ subscription = None
- EMAIL_DIGEST_CONTROL.set_value(digest_control)
+ return (not subscription) or subscription.last_view < q.last_activity_at
+ def get_for_user(self, user):
+ user_tags = list(user.marked_tags.filter(user_selections__reason='good'))
- def send_digest(self, name, char_in_db, control_date):
- new_questions, question_records = self.prepare_activity(control_date)
- new_users = User.objects.filter(date_joined__gt=control_date)
+ if len(user_tags) < TRY_N_USER_TAGS:
+ user_tags += list(Tag.objects.filter(models.Q(nodes__author=user) | models.Q(nodes__children__author=user)) \
+ .annotate(user_tag_usage_count=models.Count('name')).order_by('-user_tag_usage_count')[:TRY_N_USER_TAGS - len(user_tags)])
- digest_subject = settings.EMAIL_SUBJECT_PREFIX + _('Daily digest')
+ user_tag_names = set([t.name for t in user_tags])
- users = User.objects.filter(subscription_settings__enable_notifications=True)
- msgs = []
+ subscriptions = user.subscriptions.filter(added_at__lt=self.from_date, last_activity_at__gt=models.F('questionsubscription__last_view')
+ ).order_by('-questionsubscription__last_view')[:SUB_QUESTION_LIST_LENGTH]
- for u in users:
- context = {
- 'user': u,
- 'digest_type': name,
- }
+ unseen_questions = [q for q in self.questions if self.unseen_question(user, q)]
- if u.subscription_settings.member_joins == char_in_db:
- context['new_users'] = new_users
- else:
- context['new_users'] = False
+ interesting = []
- if u.subscription_settings.subscribed_questions == char_in_db:
- activity_in_subscriptions = []
+ for q in unseen_questions:
+ if len(set(q.tagname_list()) & user_tag_names): interesting.append(q)
- for id, r in question_records.items():
- try:
- subscription = QuestionSubscription.objects.get(question=r.question, user=u)
- record = r.get_activity_since(subscription.last_view)
+ may_help = []
+ if len(interesting):
+ if len(interesting) > SUB_QUESTION_LIST_LENGTH:
+ may_help = interesting[SUB_QUESTION_LIST_LENGTH:][-SUB_QUESTION_LIST_LENGTH:]
+ interesting = interesting[:SUB_QUESTION_LIST_LENGTH]
+ else:
+ interesting = unseen_questions[:SUB_QUESTION_LIST_LENGTH]
- if not u.subscription_settings.notify_answers:
- del record['answers']
+ return {'interesting': interesting, 'may_help': may_help, 'subscriptions': subscriptions}
- if not u.subscription_settings.notify_comments:
- if u.subscription_settings.notify_comments_own_post:
- record.comments = [a for a in record.comments if a.user == u]
- record['own_comments_only'] = True
- else:
- del record['comments']
- if not u.subscription_settings.notify_accepted:
- del record['accepted']
- if record.get('answers', False) or record.get('comments', False) or record.get('accepted', False
- ):
- activity_in_subscriptions.append({'question': r.question, 'activity': record})
- except:
- pass
- context['activity_in_subscriptions'] = activity_in_subscriptions
- else:
- context['activity_in_subscriptions'] = False
+class Command(NoArgsCommand):
+ def handle_noargs(self, **options):
+ try:
+ translation.activate(settings.LANGUAGE_CODE)
+ except:
+ logging.error("Unable to set the locale in the send emails cron job")
- if u.subscription_settings.new_question == char_in_db:
- context['new_questions'] = new_questions
- context['watched_tags_only'] = False
- elif u.subscription_settings.new_question_watched_tags == char_in_db:
- context['new_questions'] = [q for q in new_questions if
- q.tags.filter(id__in=u.marked_tags.filter(user_selections__reason='good')
- ).count() > 0]
- context['watched_tags_only'] = True
- else:
- context['new_questions'] = False
+ digest_control = EMAIL_DIGEST_CONTROL.value
- if context['new_users'] or context['activity_in_subscriptions'] or context['new_questions']:
- send_email(digest_subject, [(u.username, u.email)], "notifications/digest.html", context, threaded=False
- )
+ if digest_control is None:
+ digest_control = KeyValue(key='DIGEST_CONTROL', value={
+ 'LAST_DAILY': datetime.datetime.now() - datetime.timedelta(days=1),
+ 'LAST_WEEKLY': datetime.datetime.now() - datetime.timedelta(days=1),
+ })
+ from_date = digest_control.value['LAST_DAILY']
+ digest_control.value['LAST_DAILY'] = datetime.datetime.now()
- def prepare_activity(self, since):
- all_activity = Action.objects.filter(canceled=False, action_date__gt=since, action_type__in=(
- actions.AskAction.get_type(), actions.AnswerAction.get_type(),
- actions.CommentAction.get_type(), actions.AcceptAnswerAction.get_type()
- )).order_by('action_date')
+ EMAIL_DIGEST_CONTROL.set_value(digest_control)
- question_records = {}
- new_questions = []
+ users = User.objects.filter(subscription_settings__enable_notifications=True)
+ new_members = User.objects.filter(is_active=True, date_joined__gt=from_date).annotate(n_actions=models.Count('actions')).order_by('-n_actions')
- for activity in all_activity:
- try:
- question = activity.node.abs_parent
+ new_member_count = new_members.count()
- if not question.id in question_records:
- question_records[question.id] = QuestionRecord(question)
+ if new_member_count >= SHOW_N_MORE_ACTIVE_NEW_MEMBERS:
+ new_members = new_members[:SHOW_N_MORE_ACTIVE_NEW_MEMBERS]
+ show_all_users = True
+ else:
+ show_all_users = False
- question_records[question.id].log_activity(activity)
+ digest = DigestQuestionsIndex(from_date)
- if activity.action_type == "ask":
- new_questions.append(question)
- except:
- pass
+ send_template_email(users, "notifications/digest.html", locals())
- return new_questions, question_records
-{% extends "email_base.html" %}
-{% load i18n %}
-{% load humanize %}
-{% load extra_tags %}
-
-{% block content %}
- <p>{% trans "Hello" %} {{ user.username }},</p>
-
- <p>{% blocktrans with settings.APP_SHORT_NAME as app_title %}
- This is the {{ digest_type }} activity digest for {{ app_title }}
- {% endblocktrans %}</p>
-
- {% if new_users %}
- <h3>
- {% blocktrans with new_users|length as nusers_count and new_users|length|pluralize as nusers_count_pluralize and settings.APP_SHORT_NAME as app_title %}
- {{ nusers_count }} new user{{ nusers_count_pluralize }} joined the {{ app_title }} community:
+{% load i18n extra_tags email_tags %}
+
+{% declare %}
+ prefix = settings.EMAIL_SUBJECT_PREFIX
+ app_name = settings.APP_SHORT_NAME
+ app_url = settings.APP_URL
+
+ new_member_links = html.mark_safe(", ".join([html.objlink(u, style=settings.EMAIL_ANCHOR_STYLE) for u in new_members]))
+ new_question_count = digest.count
+
+{% enddeclare %}
+
+{% email %}
+ {% subject %}{% blocktrans %}{{ prefix }} Daily digest{% endblocktrans %}{% endsubject %}
+
+ {% htmlcontent notifications/base.html %}
+ {% blocktrans %}
+ This is a brief of what's going on the {{ app_name }} community since our last update.
+ {% endblocktrans %}
+
+ {% if new_member_count %}
+ <p style="{{ p_style }}">
+ {% if show_all_users %}
+ {% blocktrans %}
+ There are {{ new_member_count }} new members in the community. {{ new_member_links }} were the most active so far.
{% endblocktrans %}
- </h3>
+ {% else %}
+ {% blocktrans %}
+ {{ new_member_links }} have joined the {{ app_name }} community.
+ {% endblocktrans %}
+ {% endif %}
+ {% endif %}
+ </p>
+
+ {% if new_question_count %}
+
+ {% declare %}
+ new_questions_link = html.hyperlink(app_url + reverse('questions') + '?sort=latest', _('new questions'), style=a_style)
+ user_questions = digest.get_for_user(recipient)
+ subscriptions_link = html.hyperlink(app_url + recipient.get_subscribed_url(), _('subscriptions'), style=a_style)
+ {% enddeclare %}
+
+ <p style="{{ p_style }}">
+ {% blocktrans %}
+ {{ new_question_count }} {{ new_questions_link }} were posted since our last update.
+ {% endblocktrans %}
+ </p>
+
+ {% if user_questions.interesting %}
+ <p style="{{ p_style }}">
+ {% trans "We think you might like the following questions:" %}
+ </p>
<ul>
- {% for nuser in new_users %}
- <li><a href="{{ settings.APP_URL }}{{ nuser.get_profile_url }}">{{ nuser.username }}</a></li>
+ {% for q in user_questions.interesting %}
+ <li>
+ <a style="{{ a_style }}" href="{{ app_url }}{{ q.get_absolute_url }}">{{ q.title }}</a>
+ </li>
{% endfor %}
</ul>
- {% endif %}
+ {% endif %}
- {% if activity_in_subscriptions %}
- <h3>
- {% blocktrans with activity_in_subscriptions|length as question_count and activity_in_subscriptions|length|pluralize as question_count_pluralize %}
- {{ question_count }} of your subscriptions have updates:
- {% endblocktrans %}
- </h3>
+ {% if user_questions.may_help %}
+ <p style="{{ p_style }}">
+ {% trans "These new questions didn't get many attention from the community, but we think you may be able to help:" %}
+ </p>
<ul>
- {% for record in activity_in_subscriptions %}
+ {% for q in user_questions.may_help %}
<li>
- {% trans "On question " %}<a href="{{ settings.APP_URL }}{{ record.question.get_absolute_url }}">{{ question_title }}" %}</a> -
- {% if record.activity.answers %}
- {% blocktrans with record.activity.answers|length as answer_count and record.activity.answers|length|pluralize as answer_count_pluralize %}
- {{ answer_count }} new answer{{ answer_count_pluralize }}
- {% endblocktrans %},
- {% endif %}
- {% if record.activity.comments %}
- {% blocktrans with record.activity.comments|length as comment_count and record.activity.comments|length|pluralize as comment_count_pluralize %}
- {{ comment_count }} new comment{{ comment_count_pluralize }}
- {% endblocktrans %}
- {% if own_comments_only %}
- {% trans "on your own post(s)" %}
- {% endif %},
- {% endif %}
- {% if record.accepted %}
- {% trans "an answer was accepted" %}
- {% endif %}
+ <a style="{{ a_style }}" href="{{ app_url }}{{ q.get_absolute_url }}">{{ q.title }}</a>
</li>
{% endfor %}
</ul>
- {% endif %}
- {% if new_questions %}
- <h3>
- {% blocktrans with new_questions|length as question_count and new_questions|length|pluralize as question_count_pluralize%}
- {{ question_count }} new question{{ question_count_pluralize }}
- {% endblocktrans %}
- {% if watched_tags_only %}
- {% trans "matching your interesting tags" %}
- {% endif %}
- {% trans "posted :" %}
- </h3>
+ {% if user_questions.subscriptions %}
+ <p style="{{ p_style }}">
+ {% blocktrans %}Meanwhile, some of your {{ subscriptions_link }} have new updates since you last visited them:{% endblocktrans %}
+ </p>
<ul>
- {% for question in new_questions %}
+ {% for q in user_questions.subscriptions %}
<li>
- <a href="{{ settings.APP_URL }}{{ question.get_absolute_url }}">{{ question.title }}</a> -
- {% blocktrans with question.author.username as author_name and question.added_at|date:"D d M Y" as question_time %}
- Posted by {{ author_name }} in {{ question_time }}
- {% endblocktrans %}
+ <a style="{{ a_style }}" href="{{ app_url }}{{ q.get_absolute_url }}">{{ q.title }}</a>
</li>
{% endfor %}
</ul>
- {% endif %}
+ {% endif %}
+
+ {% endif %}
+
+ {% endif %}
+
+ {% endhtmlcontent %}
+
+ {% textcontent notifications/base_text.html %}
-{% endblock %}
\ No newline at end of file
+ {% endtextcontent %}
+{% endemail %}
\ No newline at end of file