Skip to content

Commit

Permalink
improved email subscription job, some small fixes, users now can chan…
Browse files Browse the repository at this point in the history
…ge their screen name any time
  • Loading branch information
evgenyfadeev committed Nov 24, 2009
1 parent 007d452 commit fa98d95
Show file tree
Hide file tree
Showing 10 changed files with 1,510 additions and 51 deletions.
7 changes: 3 additions & 4 deletions TODO
@@ -1,4 +1,3 @@
*check change email function - there is a strange 'password' field in form
*make reusable question-block template for index questions unanswered - get rid of copy-paste
*unused votes count in user profile not working - left that commented out in templates/user_info.html
*badge award notification messages need to be set fixed at place where badges are awarded
* per-tag email subscriptions
* make sorting tabs work in question search
* allow multiple logins to the same account
2 changes: 2 additions & 0 deletions forum/const.py
Expand Up @@ -49,6 +49,7 @@
TYPE_ACTIVITY_UPDATE_TAGS=15
TYPE_ACTIVITY_FAVORITE=16
TYPE_ACTIVITY_USER_FULL_UPDATED = 17
TYPE_ACTIVITY_QUESTION_EMAIL_UPDATE_SENT = 18
#TYPE_ACTIVITY_EDIT_QUESTION=17
#TYPE_ACTIVITY_EDIT_ANSWER=18

Expand All @@ -70,6 +71,7 @@
(TYPE_ACTIVITY_UPDATE_TAGS, _('updated tags')),
(TYPE_ACTIVITY_FAVORITE, _('selected favorite')),
(TYPE_ACTIVITY_USER_FULL_UPDATED, _('completed user profile')),
(TYPE_ACTIVITY_QUESTION_EMAIL_UPDATE_SENT, _('email update sent to user')),
)

TYPE_RESPONSE = {
Expand Down
4 changes: 3 additions & 1 deletion forum/forms.py
Expand Up @@ -4,7 +4,7 @@
from models import *
from const import *
from django.utils.translation import ugettext as _
from django_authopenid.forms import NextUrlField
from django_authopenid.forms import NextUrlField, UserNameField
import settings

class TitleField(forms.CharField):
Expand Down Expand Up @@ -195,6 +195,7 @@ def __init__(self, answer, revision, *args, **kwargs):

class EditUserForm(forms.Form):
email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
username = UserNameField(label=_('Screen name'))
realname = forms.CharField(label=_('Real name'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
website = forms.URLField(label=_('Website'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
city = forms.CharField(label=_('Location'), required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35}))
Expand All @@ -203,6 +204,7 @@ class EditUserForm(forms.Form):

def __init__(self, user, *args, **kwargs):
super(EditUserForm, self).__init__(*args, **kwargs)
self.fields['username'].initial = user.username
self.fields['email'].initial = user.email
self.fields['realname'].initial = user.real_name
self.fields['website'].initial = user.website
Expand Down
134 changes: 93 additions & 41 deletions forum/management/commands/send_email_alerts.py
Expand Up @@ -2,11 +2,14 @@
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
import settings
import logging
from utils.odict import OrderedDict

class Command(NoArgsCommand):
def handle_noargs(self,**options):
Expand All @@ -18,10 +21,10 @@ def handle_noargs(self,**options):
connection.close()

def get_updated_questions_for_user(self,user):
q_sel = []
q_ask = []
q_ans = []
q_all = []
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,
Expand All @@ -39,12 +42,12 @@ def get_updated_questions_for_user(self,user):
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)
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
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
Expand All @@ -55,56 +58,92 @@ def get_updated_questions_for_user(self,user):
q_all = Q_set
q_all.cutoff_time = cutoff_time
#build list in this order
q_tbl = {}
q_list = OrderedDict()
def extend_question_list(src, dst):
if isinstance(src,list):
return
"""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]:
dst[q] = cutoff_time
if cutoff_time < dst[q]['cutoff_time']:
dst[q]['cutoff_time'] = cutoff_time
else:
dst[q] = cutoff_time
#initialise a questions metadata dictionary to use for email reporting
dst[q] = {'cutoff_time':cutoff_time}

extend_question_list(q_sel, q_tbl)
extend_question_list(q_ask, q_tbl)
extend_question_list(q_ans, q_tbl)
extend_question_list(q_all, q_tbl)
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)
out = {}
for q, cutoff_time in q_tbl.items():
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)
#get info on question edits, answer edits, comments
out[q] = {}
q_rev = QuestionRevision.objects.filter(question=q,revised_at__lt=cutoff_time)
#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)
out[q]['q_rev'] = len(q_rev)
meta_data['q_rev'] = len(q_rev)
if len(q_rev) > 0 and q.added_at == q_rev[0].revised_at:
out[q]['q_rev'] = 0
out[q]['new_q'] = True
meta_data['q_rev'] = 0
meta_data['new_q'] = True
else:
out[q]['new_q'] = False
meta_data['new_q'] = False

new_ans = Answer.objects.filter(question=q,added_at__lt=cutoff_time)
new_ans = Answer.objects.filter(question=q,\
added_at__lt=cutoff_time,\
added_at__gt=emailed_at)
new_ans = new_ans.exclude(author=user)
out[q]['new_ans'] = len(new_ans)
ans_rev = AnswerRevision.objects.filter(answer__question=q,revised_at__lt=cutoff_time)
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)
out[q]['ans_rev'] = len(ans_rev)
return out
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 __act_count(self,string,number,output):
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 = len(q_list)
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')
Expand All @@ -113,17 +152,30 @@ def send_email_alerts(self):
% {'num':num_q, 'name':user.username}

text += '<ul>'
for q, act in q_list.items():
for q, meta_data in q_list.items():
act_list = []
if act['new_q']:
act_list.append(_('new question'))
self.__act_count('%(num)d rev', act['q_rev'],act_list)
self.__act_count('%(num)d ans', act['new_ans'],act_list)
self.__act_count('%(num)d ans rev',act['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)
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]}
Expand Down
3 changes: 2 additions & 1 deletion forum/views.py
Expand Up @@ -1130,6 +1130,7 @@ def edit_user(request, id):
from django_authopenid.views import set_new_email
set_new_email(user, new_email)

user.username = sanitize_html(form.cleaned_data['username'])
user.real_name = sanitize_html(form.cleaned_data['realname'])
user.website = sanitize_html(form.cleaned_data['website'])
user.location = sanitize_html(form.cleaned_data['city'])
Expand Down Expand Up @@ -1529,7 +1530,7 @@ class Response:
def __init__(self, type, title, question_id, answer_id, time, username, user_id, content):
self.type = type
self.title = title
self.titlelink = reverse('questions') + u'%s/%s#%s' % (question_id, title, answer_id)
self.titlelink = reverse('question', args=[question_id]) + u'%s#%s' % (slugify(title), answer_id)
self.time = time
self.userlink = reverse('users') + u'%s/%s/' % (user_id, username)
self.username = username
Expand Down
2 changes: 1 addition & 1 deletion templates/content/js/com.cnprog.i18n.js
Expand Up @@ -57,7 +57,7 @@ var i18nZh = {
};

var i18nEn = {
'/':'/',
'/':'/forum/',
'need >15 points to report spam':'need >15 points to report spam ',
'>15 points requried to upvote':'>15 points required to upvote ',
'tags cannot be empty':'please enter at least one tag',
Expand Down
2 changes: 1 addition & 1 deletion templates/question_edit.html
Expand Up @@ -22,7 +22,7 @@

//toggle preview of editor
var display = true;
var txt = "[{% trans "hide preview"}%]";
var txt = "[{% trans "hide preview" %}]";
$('#pre-collapse').text(txt);
$('#pre-collapse').bind('click', function(){
txt = display ? "[{% trans "show preview" %}]" : "[{% trans "hide preview" %}]";
Expand Down
4 changes: 4 additions & 0 deletions templates/user_edit.html
Expand Up @@ -39,6 +39,10 @@ <h2>{% trans "Registered user" %}</h2>
<th width="100px"></th>
<th></th>
</tr>
<tr style="height:35px">
<td>{{ form.username.label_tag }}:</td>
<td>{{ form.username }} <span class="form-error"></span> {{ form.username.errors }} </td>
</tr>

<tr style="height:35px">
<td>{{ form.email.label_tag }}:</td>
Expand Down
4 changes: 2 additions & 2 deletions templates/user_votes.html
Expand Up @@ -19,9 +19,9 @@
</div>
<div style="float:left;overflow:hidden;width:750px">
{% ifequal vote.answer_id 0 %}
<span class="question-title-link"><a href="{% url question vote.question_id %}/{{ vote.title|slugify }}">{{ vote.title }}</a></span>
<span class="question-title-link"><a href="{% url question vote.question_id %}{{ vote.title|slugify }}">{{ vote.title }}</a></span>
{% else %}
<span class="answer-title-link" ><a href="{% url question vote.question_id %}/{{ vote.title|slugify }}#{{ vote.answer_id }}">{{ vote.title }}</a></span>
<span class="answer-title-link" ><a href="{% url question vote.question_id %}{{ vote.title|slugify }}#{{ vote.answer_id }}">{{ vote.title }}</a></span>
{% endifequal %}
<div style="height:5px"></div>
</div>
Expand Down

0 comments on commit fa98d95

Please sign in to comment.