summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrei <mamoutkine@gmail.com>2011-04-15 19:07:48 -0400
committerAndrei <mamoutkine@gmail.com>2011-04-15 19:07:48 -0400
commit46b135059052f57e3105ee5236059350e456a80d (patch)
treecef5500935dc2e0d8b3aca3d8bad7294a2f2b168
parentca1e820372c11260b703681e644907117354229e (diff)
downloadaskbot-46b135059052f57e3105ee5236059350e456a80d.tar.gz
askbot-46b135059052f57e3105ee5236059350e456a80d.tar.bz2
askbot-46b135059052f57e3105ee5236059350e456a80d.zip
added an send_unanswered_question_reminders management command, untested
-rw-r--r--askbot/conf/email.py37
-rw-r--r--askbot/const/__init__.py5
-rw-r--r--askbot/management/commands/send_email_alerts.py98
-rw-r--r--askbot/management/commands/send_unanswered_question_reminders.py91
-rw-r--r--askbot/models/__init__.py43
-rw-r--r--askbot/models/question.py38
-rw-r--r--askbot/tests/email_alert_tests.py3
7 files changed, 230 insertions, 85 deletions
diff --git a/askbot/conf/email.py b/askbot/conf/email.py
index f18a554d..b86b7330 100644
--- a/askbot/conf/email.py
+++ b/askbot/conf/email.py
@@ -57,6 +57,43 @@ settings.register(
settings.register(
livesettings.BooleanValue(
EMAIL,
+ 'ENABLE_UNANSWERED_REMINDERS',
+ default = False,
+ description = _('Send periodic reminders about unanswered questions'),
+ help_text = _(
+ 'NOTE: in order to use this feature, it is necessary to '
+ 'run the management command "send_unanswered_question_reminders" '
+ '(for example, via a cron job - with an appropriate frequency)'
+ )
+ )
+)
+
+settings.register(
+ livesettings.IntegerValue(
+ EMAIL,
+ 'DAYS_BEFORE_SENDING_UNANSWERED_REMINDER',
+ default = 1,
+ description = _(
+ 'Days before starting to send reminders about unanswered questions'
+ ),
+ )
+)
+
+settings.register(
+ livesettings.IntegerValue(
+ EMAIL,
+ 'UNANSWERED_REMINDER_FREQUENCY',
+ default = 1,
+ description = _(
+ 'How often to send unanswered question reminders '
+ '(in days between the reminders sent).'
+ )
+ )
+)
+
+settings.register(
+ livesettings.BooleanValue(
+ EMAIL,
'EMAIL_VALIDATION',
default=False,
hidden=True,
diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py
index 5a0781f0..ea9236b6 100644
--- a/askbot/const/__init__.py
+++ b/askbot/const/__init__.py
@@ -105,6 +105,7 @@ TYPE_ACTIVITY_FAVORITE=16
TYPE_ACTIVITY_USER_FULL_UPDATED = 17
TYPE_ACTIVITY_EMAIL_UPDATE_SENT = 18
TYPE_ACTIVITY_MENTION = 19
+TYPE_ACTIVITY_UNANSWERED_REMINDER_SENT = 20
#TYPE_ACTIVITY_EDIT_QUESTION=17
#TYPE_ACTIVITY_EDIT_ANSWER=18
@@ -128,6 +129,10 @@ TYPE_ACTIVITY = (
(TYPE_ACTIVITY_FAVORITE, _('selected favorite')),
(TYPE_ACTIVITY_USER_FULL_UPDATED, _('completed user profile')),
(TYPE_ACTIVITY_EMAIL_UPDATE_SENT, _('email update sent to user')),
+ (
+ TYPE_ACTIVITY_UNANSWERED_REMINDER_SENT,
+ _('reminder about unanswered questions sent'),
+ ),
(TYPE_ACTIVITY_MENTION, _('mentioned in the post')),
)
diff --git a/askbot/management/commands/send_email_alerts.py b/askbot/management/commands/send_email_alerts.py
index 56286981..5f14618b 100644
--- a/askbot/management/commands/send_email_alerts.py
+++ b/askbot/management/commands/send_email_alerts.py
@@ -5,6 +5,7 @@ from django.db.models import Q, F
from askbot.models import User, Question, Answer, Tag, QuestionRevision
from askbot.models import AnswerRevision, Activity, EmailFeedSetting
from askbot.models import Comment
+from askbot.models.question import get_tag_summary_from_questions
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from django.conf import settings as django_settings
@@ -72,52 +73,6 @@ def format_action_count(string, number, output):
if number > 0:
output.append(_(string) % {'num':number})
-def get_update_subject_line(question_dict):
- """forms a subject line based on up to five most
- frequently used tags in the question_dict
-
- question_dict is an instance of SortedDict,
- where questions are keys and values are meta_data
- accumulated during the question filtering
- """
- #todo: in python 2.6 there is collections.Counter() thing
- #which would be very useful here
- tag_counts = dict()
- updated_questions = question_dict.keys()
- for question in updated_questions:
- tag_names = question.get_tag_names()
- for tag_name in tag_names:
- if tag_name in tag_counts:
- tag_counts[tag_name] += 1
- else:
- tag_counts[tag_name] = 1
- tag_list = tag_counts.keys()
- #sort in descending order
- tag_list.sort(lambda x, y: cmp(tag_counts[y], tag_counts[x]))
-
- question_count = len(updated_questions)
- #note that double quote placement is important here
- if len(tag_list) == 1:
- last_topic = '"'
- elif len(tag_list) <= 5:
- last_topic = _('" and "%s"') % tag_list.pop()
- else:
- tag_list = tag_list[:5]
- last_topic = _('" and more')
-
- topics = '"' + '", "'.join(tag_list) + last_topic
-
- subject_line = ungettext(
- '%(question_count)d updated question about %(topics)s',
- '%(question_count)d updated questions about %(topics)s',
- question_count
- ) % {
- 'question_count': question_count,
- 'topics': topics
- }
-
- return mail.prefix_the_subject_line(subject_line)
-
class Command(NoArgsCommand):
def handle_noargs(self, **options):
try:
@@ -244,41 +199,8 @@ class Command(NoArgsCommand):
q_ans_B.cutoff_time = cutoff_time
elif feed.feed_type == 'q_all':
- if user.email_tag_filter_strategy == 'ignored':
-
- ignored_tags = Tag.objects.filter(
- user_selections__reason='bad',
- user_selections__user=user
- )
-
- wk = user.ignored_tags.strip().split()
- ignored_by_wildcards = Tag.objects.get_by_wildcards(wk)
-
- q_all_A = Q_set_A.exclude(
- tags__in = ignored_tags
- ).exclude(
- tags__in = ignored_by_wildcards
- )
-
- q_all_B = Q_set_B.exclude(
- tags__in = ignored_tags
- ).exclude(
- tags__in = ignored_by_wildcards
- )
- else:
- selected_tags = Tag.objects.filter(
- user_selections__reason='good',
- user_selections__user=user
- )
-
- wk = user.interesting_tags.strip().split()
- selected_by_wildcards = Tag.objects.get_by_wildcards(wk)
-
- tag_filter = Q(tags__in = list(selected_tags)) \
- | Q(tags__in = list(selected_by_wildcards))
-
- q_all_A = Q_set_A.filter( tag_filter )
- q_all_B = Q_set_B.filter( tag_filter )
+ q_all_A = user.get_tag_filtered_questions(Q_set_A)
+ q_all_B = user.get_tag_filtered_questions(Q_set_B)
q_all_A = q_all_A[:askbot_settings.MAX_ALERTS_PER_EMAIL]
q_all_B = q_all_B[:askbot_settings.MAX_ALERTS_PER_EMAIL]
@@ -478,7 +400,19 @@ class Command(NoArgsCommand):
num_q += 1
if num_q > 0:
url_prefix = askbot_settings.APP_URL
- subject_line = get_update_subject_line(q_list)
+
+ tag_summary = get_tag_summary_from_questions(q_list.keys())
+ question_count = len(q_list.keys())
+
+ subject_line = ungettext(
+ '%(question_count)d updated question about %(topics)s',
+ '%(question_count)d updated questions about %(topics)s',
+ question_count
+ ) % {
+ 'question_count': question_count,
+ 'topics': tag_summary
+ }
+
#todo: send this to special log
#print 'have %d updated questions for %s' % (num_q, user.username)
text = ungettext('%(name)s, this is an update message header for %(num)d question',
diff --git a/askbot/management/commands/send_unanswered_question_reminders.py b/askbot/management/commands/send_unanswered_question_reminders.py
new file mode 100644
index 00000000..adef4a2d
--- /dev/null
+++ b/askbot/management/commands/send_unanswered_question_reminders.py
@@ -0,0 +1,91 @@
+import datetime
+from django.core.management.base import NoArgsCommand
+from django.conf import settings as django_settings
+from askbot import models
+from askbot import const
+from askbot.conf import settings as askbot_settings
+from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext
+from askbot.utils import mail
+from askbot.models.question import get_tag_summary_from_questions
+
+DEBUG_THIS_COMMAND = False
+
+class Command(NoArgsCommand):
+ def handle_noargs(self, **options):
+ if askbot_settings.ENABLE_UANSWERED_REMINDERS == False:
+ return
+ #get questions without answers, excluding closed and deleted
+ #order it by descending added_at date
+ wait_period = datetime.timedelta(
+ askbot_settings.DAYS_BEFORE_SENDING_UNANSWERED_REMINDER
+ )
+ cutoff_date = datetime.datetime.now() + wait_period
+
+ questions = models.Question.objects.exclude(
+ closed = True
+ ).exclude(
+ deleted = False
+ ).exclude(
+ added_at__lt = cutoff_date
+ ).filter(
+ answer_count__gt = 0
+ ).order_by('-added_at')
+ #for all users, excluding blocked
+ #for each user, select a tag filtered subset
+ #format the email reminder and send it
+ for user in models.User.objects.exclude(status = 'b'):
+ user_questions = questions.exclude(author = user)
+ user_questions = user.get_tag_filtered_questions(questions)
+
+ final_question_list = list()
+ #todo: rewrite using query set filter
+ #may be a lot more efficient
+ for question in user_questions:
+ activity_type = const.TYPE_ACTIVITY_UNANSWERED_REMINDER_SENT
+ activity, created = models.Activity.objects.get_or_create(
+ user = user,
+ question = question,
+ activity_type = activity_type
+ )
+
+ now = datetime.datetime.now()
+ recurrence_delay = datetime.timedelta(
+ askbot_settings.UNANSWERED_REMINDER_FREQUENCY
+ )
+ if created == False:
+ if activity.active_at >= now + recurrence_delay:
+ continue
+
+ activity.active_at = datetime.datetime.now()
+ activity.save()
+
+ question_count = len(final_question_list)
+ if question_count == 0:
+ continue
+
+ tag_summary = get_tag_summary_from_questions(user_questions)
+ subject_line = ungettext(
+ '%(question_count)d unanswered question about %(topics)s',
+ '%(question_count)d unanswered questions about %(topics)s',
+ question_count
+ ) % {
+ 'question_count': question_count,
+ 'topics': tag_summary
+ }
+
+ body_text = '<ul>'
+ for question in final_question_list:
+ body_text += '<li><a href="%s%s?sort=latest">%s</a></li>' \
+ % (
+ askbot_settings.APP_URL,
+ question.get_absolute_url(),
+ question.title
+ )
+ body_text += '</ul>'
+
+ mail.send_mail(
+ subject_line = subject_line,
+ body_text = body_text,
+ recipient_list = (user.email,)
+ )
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index a4d21caa..75426cf5 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -1492,6 +1492,43 @@ def user_get_q_sel_email_feed_frequency(self):
raise e
return feed_setting.frequency
+def user_get_tag_filtered_questions(self, questions = None):
+ """Returns a query set of questions, tag filtered according
+ to the user choices. Parameter ``questions`` can be either ``None``
+ or a starting query set.
+ """
+ if questions == None:
+ questions = Question.objects.all()
+
+ if self.email_tag_filter_strategy == 'ignored':
+
+ ignored_tags = Tag.objects.filter(
+ user_selections__reason = 'bad',
+ user_selections__user = self
+ )
+
+ wk = user.ignored_tags.strip().split()
+ ignored_by_wildcards = Tag.objects.get_by_wildcards(wk)
+
+ return questions.exclude(
+ tags__in = ignored_tags
+ ).exclude(
+ tags__in = ignored_by_wildcards
+ )
+ else:
+ selected_tags = Tag.objects.filter(
+ user_selections__reason = 'good',
+ user_selections__user = self
+ )
+
+ wk = user.interesting_tags.strip().split()
+ selected_by_wildcards = Tag.objects.get_by_wildcards(wk)
+
+ tag_filter = Q(tags__in = list(selected_tags)) \
+ | Q(tags__in = list(selected_by_wildcards))
+
+ return questions.filter( tag_filter )
+
def get_messages(self):
messages = []
for m in self.message_set.all():
@@ -1831,10 +1868,14 @@ User.add_to_class('downvote', downvote)
User.add_to_class('flag_post', flag_post)
User.add_to_class('receive_reputation', user_receive_reputation)
User.add_to_class('get_flags', user_get_flags)
-User.add_to_class('get_flag_count_posted_today', user_get_flag_count_posted_today)
+User.add_to_class(
+ 'get_flag_count_posted_today',
+ user_get_flag_count_posted_today
+)
User.add_to_class('get_flags_for_post', user_get_flags_for_post)
User.add_to_class('get_profile_url', get_profile_url)
User.add_to_class('get_profile_link', get_profile_link)
+User.add_to_class('get_tag_filtered_questions', user_get_tag_filtered_questions)
User.add_to_class('get_messages', get_messages)
User.add_to_class('delete_messages', delete_messages)
User.add_to_class('toggle_favorite_question', toggle_favorite_question)
diff --git a/askbot/models/question.py b/askbot/models/question.py
index d1570877..ccb587e7 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -40,6 +40,42 @@ QUESTION_ORDER_BY_MAP = {
'relevance-desc': None#this is a special case for postges only
}
+def get_tag_summary_from_questions(questions):
+ """returns a humanized string containing up to
+ five most frequently used
+ unique tags coming from the ``questions``.
+ Variable ``questions`` is an iterable of
+ :class:`~askbot.models.Question` model objects.
+
+ This is not implemented yet as a query set method,
+ because it is used on a list.
+ """
+ #todo: in python 2.6 there is collections.Counter() thing
+ #which would be very useful here
+ tag_counts = dict()
+ for question in questions:
+ tag_names = question.get_tag_names()
+ for tag_name in tag_names:
+ if tag_name in tag_counts:
+ tag_counts[tag_name] += 1
+ else:
+ tag_counts[tag_name] = 1
+ tag_list = tag_counts.keys()
+ #sort in descending order
+ tag_list.sort(lambda x, y: cmp(tag_counts[y], tag_counts[x]))
+
+ #note that double quote placement is important here
+ if len(tag_list) == 1:
+ last_topic = '"'
+ elif len(tag_list) <= 5:
+ last_topic = _('" and "%s"') % tag_list.pop()
+ else:
+ tag_list = tag_list[:5]
+ last_topic = _('" and more')
+
+ return '"' + '", "'.join(tag_list) + last_topic
+
+
class QuestionQuerySet(models.query.QuerySet):
"""Custom query set subclass for :class:`~askbot.models.Question`
"""
@@ -112,7 +148,7 @@ class QuestionQuerySet(models.query.QuerySet):
return self.extra(**extra_kwargs)
else:
#fallback to dumb title match search
- return extra(
+ return self.extra(
where=['title like %s'],
params=['%' + search_query + '%']
)
diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py
index de801967..84d9857b 100644
--- a/askbot/tests/email_alert_tests.py
+++ b/askbot/tests/email_alert_tests.py
@@ -13,6 +13,7 @@ from askbot import models
from askbot.utils import mail
from askbot.conf import settings as askbot_settings
from askbot import const
+from askbot.models.question import get_tag_summary_from_questions
def email_alert_test(test_func):
"""decorator for test methods in
@@ -686,7 +687,7 @@ class DelayedAlertSubjectLineTests(TestCase):
q8:'', q9:'', q10:'', q11:'',
}
from askbot.management.commands import send_email_alerts as cmd
- subject = cmd.get_update_subject_line(q_dict)
+ subject = get_tag_summary_from_questions(q_dict.keys())
self.assertTrue('one' not in subject)
self.assertTrue('two' in subject)