-from django.core.management.base import NoArgsCommand
-from django.db import connection
-from django.db.models import Q, F
-from forum.models import *
-from forum import const
-from django.core.mail import EmailMessage
-from django.utils.translation import ugettext as _
-from django.utils.translation import ungettext
import datetime
-from django.conf import settings
+from forum.models import *
+from forum import settings
+from django.db import models
+from forum.utils.mail import send_template_email
+from django.core.management.base import NoArgsCommand
+from forum.settings.email import EMAIL_DIGEST_FLAG
+from django.utils import translation
import logging
-from forum.utils.odict import OrderedDict
+
+SHOW_N_MORE_ACTIVE_NEW_MEMBERS = 5
+SUB_QUESTION_LIST_LENGTH = 5
+TRY_N_USER_TAGS = 5
+
+class DigestQuestionsIndex(object):
+ def __init__(self, from_date):
+ self.from_date = from_date
+
+ 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'))
+
+ hotness = lambda q: 3*q.child_count + q.n_actions
+
+ for q in new_questions:
+ q.hotness=hotness(q)
+
+ self.questions = sorted(new_questions, lambda q1, q2: q2.hotness - q1.hotness)
+ self.count = len(self.questions)
+
+ def unseen_question(self, user, question):
+ try:
+ subscription = QuestionSubscription.objects.get(question=q, user=user)
+ except:
+ subscription = None
+
+ 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'))
+
+ 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)])
+
+ user_tag_names = set([t.name for t in user_tags])
+
+
+ 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]
+
+ unseen_questions = [q for q in self.questions if self.unseen_question(user, q)]
+
+ interesting = []
+
+ for q in unseen_questions:
+ if len(set(q.tagname_list()) & user_tag_names): interesting.append(q)
+
+
+ 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]
+
+ return {'interesting': interesting, 'may_help': may_help, 'subscriptions': subscriptions}
+
+
+
class Command(NoArgsCommand):
- def handle_noargs(self,**options):
+ def handle_noargs(self, **options):
try:
- try:
- self.send_email_alerts()
- except Exception, e:
- print e
- finally:
- connection.close()
-
- def get_updated_questions_for_user(self,user):
- q_sel = None
- q_ask = None
- q_ans = None
- q_all = None
- now = datetime.datetime.now()
- Q_set1 = Question.objects.exclude(
- last_activity_by=user,
- ).exclude(
- last_activity_at__lt=user.date_joined
- ).filter(
- Q(viewed__who=user,viewed__when__lt=F('last_activity_at')) | \
- ~Q(viewed__who=user)
- ).exclude(
- deleted=True
- ).exclude(
- closed=True
- )
-
- user_feeds = EmailFeedSetting.objects.filter(subscriber=user).exclude(frequency='n')
- for feed in user_feeds:
- cutoff_time = now - EmailFeedSetting.DELTA_TABLE[feed.frequency]
- if feed.reported_at == None or feed.reported_at <= cutoff_time:
- Q_set = Q_set1.exclude(last_activity_at__gt=cutoff_time)#report these excluded later
- feed.reported_at = now
- feed.save()#may not actually report anything, depending on filters below
- if feed.feed_type == 'q_sel':
- q_sel = Q_set.filter(followed_by=user)
- q_sel.cutoff_time = cutoff_time #store cutoff time per query set
- elif feed.feed_type == 'q_ask':
- q_ask = Q_set.filter(author=user)
- q_ask.cutoff_time = cutoff_time
- elif feed.feed_type == 'q_ans':
- q_ans = Q_set.filter(answers__author=user)
- q_ans.cutoff_time = cutoff_time
- elif feed.feed_type == 'q_all':
- if user.tag_filter_setting == 'ignored':
- ignored_tags = Tag.objects.filter(user_selections__reason='bad',user_selections__user=user)
- q_all = Q_set.exclude( tags__in=ignored_tags )
- else:
- selected_tags = Tag.objects.filter(user_selections__reason='good',user_selections__user=user)
- q_all = Q_set.filter( tags__in=selected_tags )
- q_all.cutoff_time = cutoff_time
- #build list in this order
- q_list = OrderedDict()
- def extend_question_list(src, dst):
- """src is a query set with questions
- or an empty list
- dst - is an ordered dictionary
- """
- if src is None:
- return #will not do anything if subscription of this type is not used
- cutoff_time = src.cutoff_time
- for q in src:
- if q in dst:
- if cutoff_time < dst[q]['cutoff_time']:
- dst[q]['cutoff_time'] = cutoff_time
- else:
- #initialise a questions metadata dictionary to use for email reporting
- dst[q] = {'cutoff_time':cutoff_time}
-
- extend_question_list(q_sel, q_list)
- extend_question_list(q_ask, q_list)
- extend_question_list(q_ans, q_list)
- extend_question_list(q_all, q_list)
-
- ctype = ContentType.objects.get_for_model(Question)
- EMAIL_UPDATE_ACTIVITY = const.TYPE_ACTIVITY_QUESTION_EMAIL_UPDATE_SENT
- for q, meta_data in q_list.items():
- #todo use Activity, but first start keeping more Activity records
- #act = Activity.objects.filter(content_type=ctype, object_id=q.id)
- #because currently activity is not fully recorded to through
- #revision records to see what kind modifications were done on
- #the questions and answers
- try:
- update_info = Activity.objects.get(content_type=ctype,
- object_id=q.id,
- activity_type=EMAIL_UPDATE_ACTIVITY)
- emailed_at = update_info.active_at
- except Activity.DoesNotExist:
- update_info = Activity(user=user, content_object=q, activity_type=EMAIL_UPDATE_ACTIVITY)
- emailed_at = datetime.datetime(1970,1,1)#long time ago
- except Activity.MultipleObjectsReturned:
- raise Exception('server error - multiple question email activities found per user-question pair')
-
- q_rev = QuestionRevision.objects.filter(question=q,\
- revised_at__lt=cutoff_time,\
- revised_at__gt=emailed_at)
- q_rev = q_rev.exclude(author=user)
- meta_data['q_rev'] = len(q_rev)
- if len(q_rev) > 0 and q.added_at == q_rev[0].revised_at:
- meta_data['q_rev'] = 0
- meta_data['new_q'] = True
- else:
- meta_data['new_q'] = False
-
- new_ans = Answer.objects.filter(question=q,\
- added_at__lt=cutoff_time,\
- added_at__gt=emailed_at)
- new_ans = new_ans.exclude(author=user)
- meta_data['new_ans'] = len(new_ans)
- ans_rev = AnswerRevision.objects.filter(answer__question=q,\
- revised_at__lt=cutoff_time,\
- revised_at__gt=emailed_at)
- ans_rev = ans_rev.exclude(author=user)
- meta_data['ans_rev'] = len(ans_rev)
- if len(q_rev) == 0 and len(new_ans) == 0 and len(ans_rev) == 0:
- meta_data['nothing_new'] = True
- else:
- meta_data['nothing_new'] = False
- update_info.active_at = now
- update_info.save() #save question email update activity
- return q_list
-
- def __action_count(self,string,number,output):
- if number > 0:
- output.append(_(string) % {'num':number})
-
- def send_email_alerts(self):
-
- #todo: move this to template
- for user in User.objects.all():
- q_list = self.get_updated_questions_for_user(user)
- num_q = 0
- num_moot = 0
- for meta_data in q_list.values():
- if meta_data['nothing_new'] == False:
- num_q += 1
- else:
- num_moot += 1
- if num_q > 0:
- url_prefix = settings.APP_URL
- subject = _('email update message subject')
- print 'have %d updated questions for %s' % (num_q, user.username)
- text = ungettext('%(name)s, this is an update message header for a question',
- '%(name)s, this is an update message header for %(num)d questions',num_q) \
- % {'num':num_q, 'name':user.username}
-
- text += '<ul>'
- for q, meta_data in q_list.items():
- act_list = []
- if meta_data['nothing_new']:
- continue
- else:
- if meta_data['new_q']:
- act_list.append(_('new question'))
- self.__action_count('%(num)d rev', meta_data['q_rev'],act_list)
- self.__action_count('%(num)d ans', meta_data['new_ans'],act_list)
- self.__action_count('%(num)d ans rev',meta_data['ans_rev'],act_list)
- act_token = ', '.join(act_list)
- text += '<li><a href="%s?sort=latest">%s</a> <font color="#777777">(%s)</font></li>' \
- % (url_prefix + q.get_absolute_url(), q.title, act_token)
- text += '</ul>'
- if num_moot > 0:
- text += '<p></p>'
- text += ungettext('There is also one question which was recently '\
- +'updated but you might not have seen its latest version.',
- 'There are also %(num)d more questions which were recently updated '\
- +'but you might not have seen their latest version.',num_moot) \
- % {'num':num_moot,}
- text += _('Perhaps you could look up previously sent forum reminders in your mailbox.')
- text += '</p>'
-
- link = url_prefix + user.get_profile_url() + '?sort=email_subscriptions'
- text += _('go to %(link)s to change frequency of email updates or %(email)s administrator') \
- % {'link':link, 'email':settings.ADMINS[0][1]}
- msg = EmailMessage(subject, text, settings.DEFAULT_FROM_EMAIL, [user.email])
- msg.content_subtype = 'html'
- msg.send()
+ translation.activate(settings.LANGUAGE_CODE)
+ except:
+ logging.error("Unable to set the locale in the send emails cron job")
+
+ digest_control = EMAIL_DIGEST_FLAG.value
+
+ if digest_control is None:
+ digest_control = {
+ 'LAST_DAILY': datetime.datetime.now() - datetime.timedelta(days=1),
+ 'LAST_WEEKLY': datetime.datetime.now() - datetime.timedelta(days=1),
+ }
+
+ from_date = digest_control['LAST_DAILY']
+ digest_control['LAST_DAILY'] = datetime.datetime.now()
+
+ EMAIL_DIGEST_FLAG.set_value(digest_control)
+
+ users = User.objects.filter(subscription_settings__enable_notifications=True,
+ subscription_settings__send_digest=True)
+
+ # Send digest only to active users
+ if settings.SEND_DIGEST_ONLY_TO_ACTIVE_USERS:
+ users = users.filter(is_active=True)
+
+ # Send digest only to users with validated emails
+ if settings.SEND_DIGEST_ONLY_TO_VALIDATED_USERS:
+ users = users.filter(email_isvalid=True)
+
+ new_members = User.objects.filter(is_active=True, date_joined__gt=from_date).annotate(n_actions=models.Count('actions')).order_by('-n_actions')
+
+ new_member_count = new_members.count()
+
+ # The number of the flagged content for the day
+ flagged_count = Flag.objects.filter(flagged_at__gt=datetime.datetime.today()-datetime.timedelta(days=1)).count()
+
+ 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
+
+ digest = DigestQuestionsIndex(from_date)
+
+ if (not new_member_count) and (not digest.count):
+ return
+
+ send_template_email(users, "notifications/digest.html", locals())
+
+