diff options
author | Adolfo Fitoria <fitoria@fitoria-laptop.(none)> | 2009-12-15 16:57:37 -0600 |
---|---|---|
committer | Adolfo Fitoria <fitoria@fitoria-laptop.(none)> | 2009-12-15 16:57:37 -0600 |
commit | 9d1fb9890b97beb55461ca34f9757bc685461130 (patch) | |
tree | 1f8f0552ba6f4ca092aaa5a5347f0ad07433f1de /forum | |
parent | ebb0f636ae8f7db4e7a2e7470e449af3d96b15c0 (diff) | |
parent | 82d35490db90878f013523c4d1a5ec3af2df8b23 (diff) | |
download | askbot-9d1fb9890b97beb55461ca34f9757bc685461130.tar.gz askbot-9d1fb9890b97beb55461ca34f9757bc685461130.tar.bz2 askbot-9d1fb9890b97beb55461ca34f9757bc685461130.zip |
Merge branch 'master' of git://github.com/evgenyfadeev/CNPROG into evgenyfadeev/master
Conflicts:
INSTALL
LICENSE
TODO
cnprog.wsgi
context.py
development.log
forum/feed.py
forum/forms.py
forum/management/commands/send_email_alerts.py
forum/managers.py
forum/models.py
forum/templatetags/extra_filters.py
forum/templatetags/extra_tags.py
forum/urls.py
forum/views.py
locale/en/LC_MESSAGES/django.mo
locale/en/LC_MESSAGES/django.po
middleware/__init__.py
middleware/anon_user.py
settings.py
settings_local.py.dist
templates/about.html
templates/authopenid/complete.html
templates/authopenid/external_legacy_login_info.html
templates/base.html
templates/base_content.html
templates/content/js/com.cnprog.admin.js
templates/content/js/com.cnprog.i18n.js
templates/content/js/com.cnprog.post.js
templates/content/js/com.cnprog.utils.js
templates/content/js/wmd/wmd.js
templates/content/style/style.css
templates/question.html
templates/questions.html
templates/unanswered.html
templates/user_email_subscriptions.html
templates/user_reputation.html
templates/user_stats.html
templates/user_votes.html
Diffstat (limited to 'forum')
-rw-r--r-- | forum/const.py | 5 | ||||
-rw-r--r-- | forum/feed.py | 4 | ||||
-rw-r--r-- | forum/forms.py | 27 | ||||
-rw-r--r-- | forum/management/commands/send_email_alerts.py | 150 | ||||
-rw-r--r-- | forum/managers.py | 11 | ||||
-rw-r--r-- | forum/models.py | 93 | ||||
-rw-r--r-- | forum/sitemap.py | 11 | ||||
-rw-r--r-- | forum/templatetags/extra_filters.py | 11 | ||||
-rw-r--r-- | forum/templatetags/extra_tags.py | 9 | ||||
-rw-r--r-- | forum/urls.py | 21 | ||||
-rw-r--r-- | forum/views.py | 575 |
11 files changed, 698 insertions, 219 deletions
diff --git a/forum/const.py b/forum/const.py index 9b9230c0..76fd4a24 100644 --- a/forum/const.py +++ b/forum/const.py @@ -49,6 +49,7 @@ TYPE_ACTIVITY_MARK_OFFENSIVE=14 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 @@ -70,6 +71,7 @@ TYPE_ACTIVITY = ( (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 = { @@ -85,3 +87,6 @@ CONST = { 'default_version' : _('initial version'), 'retagged' : _('retagged'), } + +#how to filter questions by tags in email digests? +TAG_EMAIL_FILTER_CHOICES = (('ignored', _('exclude ignored tags')),('interesting',_('allow only selected tags'))) diff --git a/forum/feed.py b/forum/feed.py index 373f8a87..ad1d5cbd 100644 --- a/forum/feed.py +++ b/forum/feed.py @@ -16,7 +16,7 @@ from models import Question import settings class RssLastestQuestionsFeed(Feed): title = settings.APP_TITLE + _(' - ')+ _('latest questions') - link = settings.APP_URL + '/' + _('questions/') + link = settings.APP_URL + '/' + _('question/') description = settings.APP_DESCRIPTION #ttl = 10 copyright = settings.APP_COPYRIGHT @@ -34,7 +34,7 @@ class RssLastestQuestionsFeed(Feed): return item.added_at def items(self, item): - return Question.objects.filter(deleted=False).order_by('-added_at')[:30] + return Question.objects.filter(deleted=False).order_by('-last_activity_at')[:30] def main(): pass diff --git a/forum/forms.py b/forum/forms.py index 5b181d48..ad2c5bac 100644 --- a/forum/forms.py +++ b/forum/forms.py @@ -4,7 +4,7 @@ from django import forms 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): @@ -195,6 +195,7 @@ class EditAnswerForm(forms.Form): 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})) @@ -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 @@ -230,6 +232,23 @@ class EditUserForm(forms.Form): raise forms.ValidationError(_('this email has already been registered, please use another one')) return self.cleaned_data['email'] +class TagFilterSelectionForm(forms.ModelForm): + tag_filter_setting = forms.ChoiceField(choices=TAG_EMAIL_FILTER_CHOICES, #imported from forum/const.py + initial='ignored', + label=_('Choose email tag filter'), + widget=forms.RadioSelect) + class Meta: + model = User + fields = ('tag_filter_setting',) + + def save(self): + before = self.instance.tag_filter_setting + super(TagFilterSelectionForm, self).save() + after = self.instance.tag_filter_setting #User.objects.get(pk=self.instance.id).tag_filter_setting + if before != after: + return True + return False + class EditUserEmailFeedsForm(forms.Form): WN = (('w',_('weekly')),('n',_('no email'))) DWN = (('d',_('daily')),('w',_('weekly')),('n',_('no email'))) @@ -245,9 +264,6 @@ class EditUserEmailFeedsForm(forms.Form): 'answered_by_me':'n', 'individually_selected':'n', } - all_questions = forms.ChoiceField(choices=DWN,initial='w', - widget=forms.RadioSelect, - label=_('Entire forum'),) asked_by_me = forms.ChoiceField(choices=DWN,initial='w', widget=forms.RadioSelect, label=_('Asked by me')) @@ -257,6 +273,9 @@ class EditUserEmailFeedsForm(forms.Form): individually_selected = forms.ChoiceField(choices=DWN,initial='w', widget=forms.RadioSelect, label=_('Individually selected')) + all_questions = forms.ChoiceField(choices=DWN,initial='w', + widget=forms.RadioSelect, + label=_('Entire forum (tag filtered)'),) def set_initial_values(self,user=None): KEY_MAP = dict([(v,k) for k,v in self.FORM_TO_MODEL_MAP.iteritems()]) diff --git a/forum/management/commands/send_email_alerts.py b/forum/management/commands/send_email_alerts.py index a25e4343..283d5683 100644 --- a/forum/management/commands/send_email_alerts.py +++ b/forum/management/commands/send_email_alerts.py @@ -2,11 +2,20 @@ from django.core.management.base import NoArgsCommand from django.db import connection from django.db.models import Q, F from forum.models import * +<<<<<<< HEAD:forum/management/commands/send_email_alerts.py +======= +from forum import const +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py from django.core.mail import EmailMessage from django.utils.translation import ugettext as _ from django.utils.translation import ungettext import datetime import settings +<<<<<<< HEAD:forum/management/commands/send_email_alerts.py +======= +import logging +from utils.odict import OrderedDict +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py class Command(NoArgsCommand): def handle_noargs(self,**options): @@ -18,10 +27,17 @@ class Command(NoArgsCommand): connection.close() def get_updated_questions_for_user(self,user): +<<<<<<< HEAD:forum/management/commands/send_email_alerts.py q_sel = [] q_ask = [] q_ans = [] q_all = [] +======= + q_sel = None + q_ask = None + q_ans = None + q_all = None +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py now = datetime.datetime.now() Q_set1 = Question.objects.exclude( last_activity_by=user, @@ -35,16 +51,28 @@ class Command(NoArgsCommand): ).exclude( closed=True ) +<<<<<<< HEAD:forum/management/commands/send_email_alerts.py +======= + +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py 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: +<<<<<<< HEAD:forum/management/commands/send_email_alerts.py 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 +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py 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) +<<<<<<< HEAD:forum/management/commands/send_email_alerts.py q_sel.cutoff_time = cutoff_time +======= + q_sel.cutoff_time = cutoff_time #store cutoff time per query set +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py elif feed.feed_type == 'q_ask': q_ask = Q_set.filter(author=user) q_ask.cutoff_time = cutoff_time @@ -52,6 +80,7 @@ class Command(NoArgsCommand): q_ans = Q_set.filter(answers__author=user) q_ans.cutoff_time = cutoff_time elif feed.feed_type == 'q_all': +<<<<<<< HEAD:forum/management/commands/send_email_alerts.py q_all = Q_set q_all.cutoff_time = cutoff_time #build list in this order @@ -97,14 +126,108 @@ class Command(NoArgsCommand): return out def __act_count(self,string,number,output): +======= + 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): +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py if number > 0: output.append(_(string) % {'num':number}) def send_email_alerts(self): +<<<<<<< HEAD:forum/management/commands/send_email_alerts.py for user in User.objects.all(): q_list = self.get_updated_questions_for_user(user) num_q = len(q_list) +======= + #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 +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py if num_q > 0: url_prefix = settings.APP_URL subject = _('email update message subject') @@ -113,6 +236,7 @@ class Command(NoArgsCommand): % {'num':num_q, 'name':user.username} text += '<ul>' +<<<<<<< HEAD:forum/management/commands/send_email_alerts.py for q, act in q_list.items(): act_list = [] if act['new_q']: @@ -124,6 +248,32 @@ class Command(NoArgsCommand): 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>' +======= + 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>' + +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py 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]} diff --git a/forum/managers.py b/forum/managers.py index 795d382e..06fae761 100644 --- a/forum/managers.py +++ b/forum/managers.py @@ -7,6 +7,7 @@ from forum.models import * from urllib import quote, unquote class QuestionManager(models.Manager): +<<<<<<< HEAD:forum/managers.py def get_translation_questions(self, orderby, page_size): questions = self.filter(deleted=False, author__id__in=[28,29]).order_by(orderby)[:page_size] return questions @@ -26,6 +27,8 @@ class QuestionManager(models.Manager): def get_questions(self, orderby): questions = self.filter(deleted=False).order_by(orderby) return questions +======= +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/managers.py def update_tags(self, question, tagnames, user): """ @@ -92,12 +95,20 @@ class QuestionManager(models.Manager): Questions with the individual tags will be added to list if above questions are not full. """ #print datetime.datetime.now() +<<<<<<< HEAD:forum/managers.py from forum.models import Question questions = list(Question.objects.filter(tagnames = question.tagnames, deleted=False).all()) tags_list = question.tags.all() for tag in tags_list: extend_questions = Question.objects.filter(tags__id = tag.id, deleted=False)[:50] +======= + questions = list(self.filter(tagnames = question.tagnames, deleted=False).all()) + + tags_list = question.tags.all() + for tag in tags_list: + extend_questions = self.filter(tags__id = tag.id, deleted=False)[:50] +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/managers.py for item in extend_questions: if item not in questions and len(questions) < 10: questions.append(item) diff --git a/forum/models.py b/forum/models.py index 39058bea..f1876588 100644 --- a/forum/models.py +++ b/forum/models.py @@ -3,6 +3,10 @@ import datetime import hashlib from urllib import quote_plus, urlencode from django.db import models, IntegrityError +<<<<<<< HEAD:forum/models.py +======= +from django.utils.http import urlquote as django_urlquote +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py from django.utils.html import strip_tags from django.core.urlresolvers import reverse from django.contrib.auth.models import User @@ -12,8 +16,18 @@ from django.template.defaultfilters import slugify from django.db.models.signals import post_delete, post_save, pre_save from django.utils.translation import ugettext as _ from django.utils.safestring import mark_safe +<<<<<<< HEAD:forum/models.py import django.dispatch import settings +======= +from django.contrib.sitemaps import ping_google +import django.dispatch +import settings +import logging + +if settings.USE_SPHINX_SEARCH == True: + from djangosphinx.models import SphinxSearch +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py from forum.managers import * from const import * @@ -96,6 +110,17 @@ class Comment(models.Model): class Meta: ordering = ('-added_at',) db_table = u'comment' +<<<<<<< HEAD:forum/models.py +======= + + def save(self,**kwargs): + super(Comment,self).save(**kwargs) + try: + ping_google() + except Exception: + logging.debug('problem pinging google did you register you sitemap with google?') + +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py def __unicode__(self): return self.comment @@ -184,8 +209,27 @@ class Question(models.Model): votes = generic.GenericRelation(Vote) flagged_items = generic.GenericRelation(FlaggedItem) +<<<<<<< HEAD:forum/models.py objects = QuestionManager() +======= + if settings.USE_SPHINX_SEARCH == True: + search = SphinxSearch( + index=' '.join(settings.SPHINX_SEARCH_INDICES), + mode='SPH_MATCH_ALL', + ) + logging.debug('have sphinx search') + + objects = QuestionManager() + + def delete(self): + super(Question, self).delete() + try: + ping_google() + except Exception: + logging.debug('problem pinging google did you register you sitemap with google?') + +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py def save(self, **kwargs): """ Overridden to manually manage addition of tags when the object @@ -196,6 +240,13 @@ class Question(models.Model): """ initial_addition = (self.id is None) super(Question, self).save(**kwargs) +<<<<<<< HEAD:forum/models.py +======= + try: + ping_google() + except Exception: + logging.debug('problem pinging google did you register you sitemap with google?') +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py if initial_addition: tags = Tag.objects.get_or_create_multiple(self.tagname_list(), self.author) @@ -210,7 +261,11 @@ class Question(models.Model): return u','.join([unicode(tag) for tag in self.tagname_list()]) def get_absolute_url(self): +<<<<<<< HEAD:forum/models.py return '%s%s' % (reverse('question', args=[self.id]), slugify(self.title)) +======= + return '%s%s' % (reverse('question', args=[self.id]), django_urlquote(slugify(self.title))) +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py def has_favorite_by_user(self, user): if not user.is_authenticated(): @@ -332,6 +387,15 @@ class FavoriteQuestion(models.Model): def __unicode__(self): return '[%s] favorited at %s' %(self.user, self.added_at) +<<<<<<< HEAD:forum/models.py +======= +class MarkedTag(models.Model): + TAG_MARK_REASONS = (('good',_('interesting')),('bad',_('ignored'))) + tag = models.ForeignKey(Tag, related_name='user_selections') + user = models.ForeignKey(User, related_name='tag_selections') + reason = models.CharField(max_length=16, choices=TAG_MARK_REASONS) + +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py class QuestionRevision(models.Model): """A revision of a Question.""" question = models.ForeignKey(Question, related_name='revisions') @@ -435,6 +499,16 @@ class Answer(models.Model): get_comments = get_object_comments get_last_update_info = post_get_last_update_info +<<<<<<< HEAD:forum/models.py +======= + def save(self,**kwargs): + super(Answer,self).save(**kwargs) + try: + ping_google() + except Exception: + logging.debug('problem pinging google did you register you sitemap with google?') + +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py def get_user_vote(self, user): votes = self.votes.filter(user=user) if votes.count() > 0: @@ -449,7 +523,11 @@ class Answer(models.Model): return self.question.title def get_absolute_url(self): +<<<<<<< HEAD:forum/models.py return '%s%s#%s' % (reverse('question', args=[self.question.id]), slugify(self.question.title), self.id) +======= + return '%s%s#%s' % (reverse('question', args=[self.question.id]), django_urlquote(slugify(self.question.title)), self.id) +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py class Meta: db_table = u'answer' @@ -590,7 +668,11 @@ class Book(models.Model): questions = models.ManyToManyField(Question, related_name='book', db_table='book_question') def get_absolute_url(self): +<<<<<<< HEAD:forum/models.py return reverse('book', args=[self.short_name]) +======= + return reverse('book', args=[django_urlquote(slugify(self.short_name))]) +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py def __unicode__(self): return self.title @@ -680,6 +762,17 @@ User.add_to_class('date_of_birth', models.DateField(null=True, blank=True)) User.add_to_class('about', models.TextField(blank=True)) User.add_to_class('is_username_taken',classmethod(user_is_username_taken)) User.add_to_class('get_q_sel_email_feed_frequency',user_get_q_sel_email_feed_frequency) +<<<<<<< HEAD:forum/models.py +======= +User.add_to_class('hide_ignored_questions', models.BooleanField(default=False)) +User.add_to_class('tag_filter_setting', + models.CharField( + max_length=16, + choices=TAG_EMAIL_FILTER_CHOICES, + default='ignored' + ) + ) +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py # custom signal tags_updated = django.dispatch.Signal(providing_args=["question"]) diff --git a/forum/sitemap.py b/forum/sitemap.py new file mode 100644 index 00000000..dc97a009 --- /dev/null +++ b/forum/sitemap.py @@ -0,0 +1,11 @@ +from django.contrib.sitemaps import Sitemap +from forum.models import Question + +class QuestionsSitemap(Sitemap): + changefreq = 'daily' + priority = 0.5 + def items(self): + return Question.objects.exclude(deleted=True) + + def lastmod(self, obj): + return obj.last_activity_at diff --git a/forum/templatetags/extra_filters.py b/forum/templatetags/extra_filters.py index 3644fdc3..865cd33d 100644 --- a/forum/templatetags/extra_filters.py +++ b/forum/templatetags/extra_filters.py @@ -1,4 +1,8 @@ from django import template +<<<<<<< HEAD:forum/templatetags/extra_filters.py +======= +from django.core import serializers +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/templatetags/extra_filters.py from forum import auth import logging @@ -91,3 +95,10 @@ def cnprog_intword(number): return number except: return number +<<<<<<< HEAD:forum/templatetags/extra_filters.py +======= + +@register.filter +def json_serialize(object): + return serializers.serialize('json',object) +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/templatetags/extra_filters.py diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py index 8bd0e128..8ed79d3c 100644 --- a/forum/templatetags/extra_tags.py +++ b/forum/templatetags/extra_tags.py @@ -251,7 +251,11 @@ def diff_date(date, limen=2): return _('2 days ago') elif days == 1: return _('yesterday') +<<<<<<< HEAD:forum/templatetags/extra_tags.py elif minutes > 60: +======= + elif minutes >= 60: +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/templatetags/extra_tags.py return ungettext('%(hr)d hour ago','%(hr)d hours ago',hours) % {'hr':hours} else: return ungettext('%(min)d min ago','%(min)d mins ago',minutes) % {'min':minutes} @@ -332,8 +336,13 @@ class BlockResourceNode(template.Node): for item in self.items: bit = item.render(context) out += bit +<<<<<<< HEAD:forum/templatetags/extra_tags.py out = out.replace(' ','') return os.path.normpath(out) + '?v=%d' % settings.RESOURCE_REVISION +======= + out = os.path.normpath(out) + '?v=%d' % settings.RESOURCE_REVISION + return out.replace(' ','') +>>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/templatetags/extra_tags.py @register.tag(name='blockresource') def blockresource(parser,token): diff --git a/forum/urls.py b/forum/urls.py index a08fe716..62e70161 100644 --- a/forum/urls.py +++ b/forum/urls.py @@ -3,16 +3,21 @@ from django.conf.urls.defaults import * from django.contrib import admin from forum import views as app from forum.feed import RssLastestQuestionsFeed +from forum.sitemap import QuestionsSitemap from django.utils.translation import ugettext as _ admin.autodiscover() feeds = { 'rss': RssLastestQuestionsFeed } +sitemaps = { + 'questions': QuestionsSitemap +} APP_PATH = os.path.dirname(os.path.dirname(__file__)) urlpatterns = patterns('', url(r'^$', app.index, name='index'), + url(r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}), (r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/content/images/favicon.ico'}), (r'^favicon\.gif$', 'django.views.generic.simple.redirect_to', {'url': '/content/images/favicon.gif'}), (r'^content/(?P<path>.*)$', 'django.views.static.serve', @@ -39,6 +44,7 @@ urlpatterns = patterns('', url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('vote/')), app.vote, name='vote'), url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')), app.question_revisions, name='question_revisions'), url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('comments/')), app.question_comments, name='question_comments'), + url(r'^%s$' % _('command/'), app.ajax_command, name='call_ajax'), url(r'^%s(?P<object_id>\d+)/%s(?P<comment_id>\d+)/%s$' % (_('questions/'), _('comments/'),_('delete/')), \ app.delete_comment, kwargs={'commented_object_type':'question'},\ @@ -50,7 +56,20 @@ urlpatterns = patterns('', #place general question item in the end of other operations url(r'^%s(?P<id>\d+)//*' % _('question/'), app.question, name='question'), url(r'^%s$' % _('tags/'), app.tags, name='tags'), - url(r'^%s(?P<tag>[^/]+)/$' % _('tags/'), app.tag), + url(r'^%s(?P<tag>[^/]+)/$' % _('tags/'), app.tag, name='tag_questions'), + + url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('interesting/')), app.mark_tag, \ + kwargs={'reason':'good','action':'add'}, \ + name='mark_interesting_tag'), + + url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('ignored/')), app.mark_tag, \ + kwargs={'reason':'bad','action':'add'}, \ + name='mark_ignored_tag'), + + url(r'^%s(?P<tag>[^/]+)/$' % _('unmark-tag/'), app.mark_tag, \ + kwargs={'action':'remove'}, \ + name='mark_ignored_tag'), + url(r'^%s$' % _('users/'),app.users, name='users'), url(r'^%s(?P<id>\d+)/$' % _('moderate-user/'), app.moderate_user, name='moderate_user'), url(r'^%s(?P<id>\d+)/%s$' % (_('users/'), _('edit/')), app.edit_user, name='edit_user'), diff --git a/forum/views.py b/forum/views.py index e4ccaa16..296745f9 100644 --- a/forum/views.py +++ b/forum/views.py @@ -15,12 +15,15 @@ from django.utils import simplejson from django.core import serializers from django.core.mail import mail_admins from django.db import transaction +from django.db.models import Count, Q from django.contrib.contenttypes.models import ContentType from django.utils.translation import ugettext as _ +from django.utils.datastructures import SortedDict from django.template.defaultfilters import slugify from django.core.exceptions import PermissionDenied from utils.html import sanitize_html +from utils.decorators import ajax_method, ajax_login_required from markdown2 import Markdown #from lxml.html.diff import htmldiff from forum.diff import textDiff as htmldiff @@ -50,7 +53,7 @@ answer_type = ContentType.objects.get_for_model(Answer) comment_type = ContentType.objects.get_for_model(Comment) question_revision_type = ContentType.objects.get_for_model(QuestionRevision) answer_revision_type = ContentType.objects.get_for_model(AnswerRevision) -repute_type =ContentType.objects.get_for_model(Repute) +repute_type = ContentType.objects.get_for_model(Repute) question_type_id = question_type.id answer_type_id = answer_type.id comment_type_id = comment_type.id @@ -61,7 +64,7 @@ def _get_tags_cache_json(): tags = Tag.objects.filter(deleted=False).all() tags_list = [] for tag in tags: - dic = {'n': tag.name, 'c': tag.used_count } + dic = {'n': tag.name, 'c': tag.used_count} tags_list.append(dic) tags = simplejson.dumps(tags_list) return tags @@ -87,14 +90,26 @@ def index(request): } view_id, orderby = _get_and_remember_questions_sort_method(request, view_dic, 'latest') - questions = Question.objects.get_questions_by_pagesize(orderby, INDEX_PAGE_SIZE) + page_size = request.session.get('pagesize', QUESTIONS_PAGE_SIZE) + questions = Question.objects.exclude(deleted=True).order_by(orderby)[:page_size] # RISK - inner join queries questions = questions.select_related() tags = Tag.objects.get_valid_tags(INDEX_TAGS_SIZE) awards = Award.objects.get_recent_awards() + (interesting_tag_names, ignored_tag_names) = (None, None) + if request.user.is_authenticated(): + pt = MarkedTag.objects.filter(user=request.user) + interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True) + ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True) + + tags_autocomplete = _get_tags_cache_json() + return render_to_response('index.html', { + 'interesting_tag_names': interesting_tag_names, + 'tags_autocomplete': tags_autocomplete, + 'ignored_tag_names': ignored_tag_names, "questions" : questions, "tab_id" : view_id, "tags" : tags, @@ -145,7 +160,7 @@ def questions(request, tagname=None, unanswered=False): List of Questions, Tagged questions, and Unanswered questions. """ # template file - # "questions.html" or "unanswered.html" + # "questions.html" or maybe index.html in the future template_file = "questions.html" # Set flag to False by default. If it is equal to True, then need to be saved. pagesize_changed = False @@ -160,18 +175,61 @@ def questions(request, tagname=None, unanswered=False): view_id, orderby = _get_and_remember_questions_sort_method(request,view_dic,'latest') # check if request is from tagged questions + qs = Question.objects.exclude(deleted=True) + if tagname is not None: - objects = Question.objects.get_questions_by_tag(tagname, orderby) - elif unanswered: - #check if request is from unanswered questions - template_file = "unanswered.html" - objects = Question.objects.get_unanswered_questions(orderby) - else: - objects = Question.objects.get_questions(orderby) + qs = qs.filter(tags__name = unquote(tagname)) - # RISK - inner join queries - objects = objects.select_related(depth=1); - objects_list = Paginator(objects, pagesize) + if unanswered: + qs = qs.exclude(answer_accepted=True) + + author_name = None + #user contributed questions & answers + if 'user' in request.GET: + try: + author_name = request.GET['user'] + u = User.objects.get(username=author_name) + qs = qs.filter(Q(author=u) | Q(answers__author=u)) + except User.DoesNotExist: + author_name = None + + if request.user.is_authenticated(): + uid_str = str(request.user.id) + qs = qs.extra( + select = SortedDict([ + ( + 'interesting_score', + 'SELECT COUNT(1) FROM forum_markedtag, question_tags ' + + 'WHERE forum_markedtag.user_id = %s ' + + 'AND forum_markedtag.tag_id = question_tags.tag_id ' + + 'AND forum_markedtag.reason = "good" ' + + 'AND question_tags.question_id = question.id' + ), + ]), + select_params = (uid_str,), + ) + if request.user.hide_ignored_questions: + ignored_tags = Tag.objects.filter(user_selections__reason='bad', + user_selections__user = request.user) + qs = qs.exclude(tags__in=ignored_tags) + else: + qs = qs.extra( + select = SortedDict([ + ( + 'ignored_score', + 'SELECT COUNT(1) FROM forum_markedtag, question_tags ' + + 'WHERE forum_markedtag.user_id = %s ' + + 'AND forum_markedtag.tag_id = question_tags.tag_id ' + + 'AND forum_markedtag.reason = "bad" ' + + 'AND question_tags.question_id = question.id' + ) + ]), + select_params = (uid_str, ) + ) + + qs = qs.select_related(depth=1).order_by(orderby) + + objects_list = Paginator(qs, pagesize) questions = objects_list.page(page) # Get related tags from this page objects @@ -179,13 +237,26 @@ def questions(request, tagname=None, unanswered=False): related_tags = Tag.objects.get_tags_by_questions(questions.object_list) else: related_tags = None + tags_autocomplete = _get_tags_cache_json() + + # get the list of interesting and ignored tags + (interesting_tag_names, ignored_tag_names) = (None, None) + if request.user.is_authenticated(): + pt = MarkedTag.objects.filter(user=request.user) + interesting_tag_names = pt.filter(reason='good').values_list('tag__name', flat=True) + ignored_tag_names = pt.filter(reason='bad').values_list('tag__name', flat=True) + return render_to_response(template_file, { "questions" : questions, + "author_name" : author_name, "tab_id" : view_id, "questions_count" : objects_list.count, "tags" : related_tags, + "tags_autocomplete" : tags_autocomplete, "searchtag" : tagname, "is_unanswered" : unanswered, + "interesting_tag_names": interesting_tag_names, + 'ignored_tag_names': ignored_tag_names, "context" : { 'is_paginated' : True, 'pages': objects_list.num_pages, @@ -546,7 +617,6 @@ def _retag_question(request, question): 'tags' : _get_tags_cache_json(), }, context_instance=RequestContext(request)) - def _edit_question(request, question): latest_revision = question.get_latest_revision() revision_form = None @@ -641,8 +711,8 @@ def edit_answer(request, id): if revision_form.is_valid(): # Replace with those from the selected revision form = EditAnswerForm(answer, - AnswerRevision.objects.get(answer=answer, - revision=revision_form.cleaned_data['revision'])) + AnswerRevision.objects.get(answer=answer, + revision=revision_form.cleaned_data['revision'])) else: form = EditAnswerForm(answer, latest_revision, request.POST) else: @@ -659,11 +729,11 @@ def edit_answer(request, id): Answer.objects.filter(id=answer.id).update(**updated_fields) revision = AnswerRevision( - answer = answer, - author = request.user, - revised_at = edited_at, - text = form.cleaned_data['text'] - ) + answer=answer, + author=request.user, + revised_at=edited_at, + text=form.cleaned_data['text'] + ) if form.cleaned_data['summary']: revision.summary = form.cleaned_data['summary'] @@ -680,14 +750,15 @@ def edit_answer(request, id): revision_form = RevisionForm(answer, latest_revision) form = EditAnswerForm(answer, latest_revision) return render_to_response('answer_edit.html', { - 'answer': answer, - 'revision_form': revision_form, - 'form' : form, - }, context_instance=RequestContext(request)) + 'answer': answer, + 'revision_form': revision_form, + 'form': form, + }, context_instance=RequestContext(request)) QUESTION_REVISION_TEMPLATE = ('<h1>%(title)s</h1>\n' - '<div class="text">%(html)s</div>\n' - '<div class="tags">%(tags)s</div>') + '<div class="text">%(html)s</div>\n' + '<div class="tags">%(tags)s</div>') + def question_revisions(request, id): post = get_object_or_404(Question, id=id) revisions = list(post.revisions.all()) @@ -706,13 +777,13 @@ def question_revisions(request, id): 'title': revisions[0].title, 'html': sanitize_html(markdowner.convert(revisions[0].text)), 'tags': ' '.join(['<a class="post-tag">%s</a>' % tag - for tag in revisions[0].tagnames.split(' ')]), + for tag in revisions[0].tagnames.split(' ')]), } revisions[i].summary = _('initial version') return render_to_response('revisions_question.html', { - 'post': post, - 'revisions': revisions, - }, context_instance=RequestContext(request)) + 'post': post, + 'revisions': revisions, + }, context_instance=RequestContext(request)) ANSWER_REVISION_TEMPLATE = ('<div class="text">%(html)s</div>') def answer_revisions(request, id): @@ -729,9 +800,9 @@ def answer_revisions(request, id): revisions[i].diff = revisions[i].text revisions[i].summary = _('initial version') return render_to_response('revisions_answer.html', { - 'post': post, - 'revisions': revisions, - }, context_instance=RequestContext(request)) + 'post': post, + 'revisions': revisions, + }, context_instance=RequestContext(request)) def answer(request, id): question = get_object_or_404(Question, id=id) @@ -744,25 +815,25 @@ def answer(request, id): if request.user.is_authenticated(): create_new_answer( - question=question, - author=request.user, - added_at=update_time, - wiki=wiki, - text=text, - email_notify=form.cleaned_data['email_notify'] - ) + question=question, + author=request.user, + added_at=update_time, + wiki=wiki, + text=text, + email_notify=form.cleaned_data['email_notify'] + ) else: request.session.flush() html = sanitize_html(markdowner.convert(text)) summary = strip_tags(html)[:120] anon = AnonymousAnswer( - question = question, - wiki = wiki, - text = text, - summary = summary, - session_key = request.session.session_key, - ip_addr = request.META['REMOTE_ADDR'], - ) + question=question, + wiki=wiki, + text=text, + summary=summary, + session_key=request.session.session_key, + ip_addr=request.META['REMOTE_ADDR'], + ) anon.save() return HttpResponseRedirect(reverse('user_signin_new_answer')) @@ -793,22 +864,21 @@ def tags(request): tags = objects_list.page(objects_list.num_pages) return render_to_response('tags.html', { - "tags" : tags, - "stag" : stag, - "tab_id" : sortby, - "keywords" : stag, - "context" : { - 'is_paginated' : is_paginated, - 'pages': objects_list.num_pages, - 'page': page, - 'has_previous': tags.has_previous(), - 'has_next': tags.has_next(), - 'previous': tags.previous_page_number(), - 'next': tags.next_page_number(), - 'base_url' : reverse('tags') + '?sort=%s&' % sortby - } - - }, context_instance=RequestContext(request)) + "tags" : tags, + "stag" : stag, + "tab_id" : sortby, + "keywords" : stag, + "context" : { + 'is_paginated' : is_paginated, + 'pages': objects_list.num_pages, + 'page': page, + 'has_previous': tags.has_previous(), + 'has_next': tags.has_next(), + 'previous': tags.previous_page_number(), + 'next': tags.next_page_number(), + 'base_url' : reverse('tags') + '?sort=%s&' % sortby + } + }, context_instance=RequestContext(request)) def tag(request, tag): return questions(request, tagname=tag) @@ -1042,6 +1112,42 @@ def vote(request, id): data = simplejson.dumps(response_data) return HttpResponse(data, mimetype="application/json") +@ajax_login_required +def mark_tag(request, tag=None, **kwargs): + action = kwargs['action'] + ts = MarkedTag.objects.filter(user=request.user, tag__name=tag) + if action == 'remove': + logging.debug('deleting tag %s' % tag) + ts.delete() + else: + reason = kwargs['reason'] + if len(ts) == 0: + try: + t = Tag.objects.get(name=tag) + mt = MarkedTag(user=request.user, reason=reason, tag=t) + mt.save() + except: + pass + else: + ts.update(reason=reason) + return HttpResponse(simplejson.dumps(''), mimetype="application/json") + +@ajax_login_required +def ajax_toggle_ignored_questions(request): + if request.user.hide_ignored_questions: + new_hide_setting = False + else: + new_hide_setting = True + request.user.hide_ignored_questions = new_hide_setting + request.user.save() + +@ajax_method +def ajax_command(request): + if 'command' not in request.POST: + return HttpResponseForbidden(mimetype="application/json") + if request.POST['command'] == 'toggle-ignored-questions': + return ajax_toggle_ignored_questions(request) + def users(request): is_paginated = True sortby = request.GET.get('sort', 'reputation') @@ -1073,22 +1179,22 @@ def users(request): users = objects_list.page(objects_list.num_pages) return render_to_response('users.html', { - "users" : users, - "suser" : suser, - "keywords" : suser, - "tab_id" : sortby, - "context" : { - 'is_paginated' : is_paginated, - 'pages': objects_list.num_pages, - 'page': page, - 'has_previous': users.has_previous(), - 'has_next': users.has_next(), - 'previous': users.previous_page_number(), - 'next': users.next_page_number(), - 'base_url' : base_url - } - - }, context_instance=RequestContext(request)) + "users" : users, + "suser" : suser, + "keywords" : suser, + "tab_id" : sortby, + "context" : { + 'is_paginated' : is_paginated, + 'pages': objects_list.num_pages, + 'page': page, + 'has_previous': users.has_previous(), + 'has_next': users.has_next(), + 'previous': users.previous_page_number(), + 'next': users.next_page_number(), + 'base_url' : base_url + } + + }, context_instance=RequestContext(request)) def user(request, id): sort = request.GET.get('sort', 'stats') @@ -1130,6 +1236,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']) @@ -1147,9 +1254,9 @@ def edit_user(request, id): else: form = EditUserForm(user) return render_to_response('user_edit.html', { - 'form' : form, - 'gravatar_faq_url' : reverse('faq') + '#gravatar', - }, context_instance=RequestContext(request)) + 'form' : form, + 'gravatar_faq_url' : reverse('faq') + '#gravatar', + }, context_instance=RequestContext(request)) def user_stats(request, user_id, user_view): user = get_object_or_404(User, id=user_id) @@ -1216,32 +1323,52 @@ def user_stats(request, user_id, user_view): 'answer_count', 'vote_up_count', 'vote_down_count')[:100] + up_votes = Vote.objects.get_up_vote_count_from_user(user) down_votes = Vote.objects.get_down_vote_count_from_user(user) votes_today = Vote.objects.get_votes_count_today_from_user(user) votes_total = VOTE_RULES['scope_votes_per_user_per_day'] - tags = user.created_tags.all().order_by('-used_count')[:50] - if settings.DJANGO_VERSION < 1.1: + + question_id_set = set(map(lambda v: v['id'], list(questions))) \ + | set(map(lambda v: v['id'], list(answered_questions))) + + user_tags = Tag.objects.filter(questions__id__in = question_id_set) + try: + from django.db.models import Count awards = Award.objects.extra( - select={'id': 'badge.id', 'count': 'count(badge_id)', 'name':'badge.name', 'description': 'badge.description', 'type': 'badge.type'}, - tables=['award', 'badge'], - order_by=['-awarded_at'], - where=['user_id=%s AND badge_id=badge.id'], - params=[user.id] - ).values('id', 'count', 'name', 'description', 'type') + select={'id': 'badge.id', + 'name':'badge.name', + 'description': 'badge.description', + 'type': 'badge.type'}, + tables=['award', 'badge'], + order_by=['-awarded_at'], + where=['user_id=%s AND badge_id=badge.id'], + params=[user.id] + ).values('id', 'name', 'description', 'type') total_awards = awards.count() - awards.query.group_by = ['badge_id'] - else: + awards = awards.annotate(count = Count('badge__id')) + user_tags = user_tags.annotate(user_tag_usage_count=Count('name')) + + except ImportError: awards = Award.objects.extra( - select={'id': 'badge.id', 'name':'badge.name', 'description': 'badge.description', 'type': 'badge.type'}, - tables=['award', 'badge'], - order_by=['-awarded_at'], - where=['user_id=%s AND badge_id=badge.id'], - params=[user.id] - ).values('id', 'name', 'description', 'type') + select={'id': 'badge.id', + 'count': 'count(badge_id)', + 'name':'badge.name', + 'description': 'badge.description', + 'type': 'badge.type'}, + tables=['award', 'badge'], + order_by=['-awarded_at'], + where=['user_id=%s AND badge_id=badge.id'], + params=[user.id] + ).values('id', 'count', 'name', 'description', 'type') total_awards = awards.count() - from django.db.models import Count - awards = awards.annotate(count = Count('badge__id')) + awards.query.group_by = ['badge_id'] + + user_tags = user_tags.extra( + select={'user_tag_usage_count': 'COUNT(1)',}, + order_by=['-user_tag_usage_count'], + ) + user_tags.query.group_by = ['name'] if auth.can_moderate_users(request.user): moderate_user_form = ModerateUserForm(instance=user) @@ -1249,22 +1376,23 @@ def user_stats(request, user_id, user_view): moderate_user_form = None return render_to_response(user_view.template_file,{ - 'moderate_user_form': moderate_user_form, - "tab_name" : user_view.id, - "tab_description" : user_view.tab_description, - "page_title" : user_view.page_title, - "view_user" : user, - "questions" : questions, - "answered_questions" : answered_questions, - "up_votes" : up_votes, - "down_votes" : down_votes, - "total_votes": up_votes + down_votes, - "votes_today_left": votes_total-votes_today, - "votes_total_per_day": votes_total, - "tags" : tags, - "awards": awards, - "total_awards" : total_awards, - }, context_instance=RequestContext(request)) + 'moderate_user_form': moderate_user_form, + "tab_name" : user_view.id, + "tab_description" : user_view.tab_description, + "page_title" : user_view.page_title, + "view_user" : user, + "questions" : questions, + "answered_questions" : answered_questions, + "up_votes" : up_votes, + "down_votes" : down_votes, + "total_votes": up_votes + down_votes, + "votes_today_left": votes_total-votes_today, + "votes_total_per_day": votes_total, + "user_tags" : user_tags[:50], + "tags" : tags, + "awards": awards, + "total_awards" : total_awards, + }, context_instance=RequestContext(request)) def user_recent(request, user_id, user_view): user = get_object_or_404(User, id=user_id) @@ -1314,7 +1442,7 @@ def user_recent(request, user_id, user_view): ) if len(questions) > 0: questions = [(Event(q['active_at'], q['activity_type'], q['title'], '', '0', \ - q['question_id'])) for q in questions] + q['question_id'])) for q in questions] activities.extend(questions) # answers @@ -1341,7 +1469,7 @@ def user_recent(request, user_id, user_view): ) if len(answers) > 0: answers = [(Event(q['active_at'], q['activity_type'], q['title'], '', q['answer_id'], \ - q['question_id'])) for q in answers] + q['question_id'])) for q in answers] activities.extend(answers) # question comments @@ -1369,7 +1497,7 @@ def user_recent(request, user_id, user_view): if len(comments) > 0: comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', '0', \ - q['question_id'])) for q in comments] + q['question_id'])) for q in comments] activities.extend(comments) # answer comments @@ -1400,7 +1528,7 @@ def user_recent(request, user_id, user_view): if len(comments) > 0: comments = [(Event(q['added_at'], q['activity_type'], q['title'], '', q['answer_id'], \ - q['question_id'])) for q in comments] + q['question_id'])) for q in comments] activities.extend(comments) # question revisions @@ -1429,7 +1557,7 @@ def user_recent(request, user_id, user_view): if len(revisions) > 0: revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], '0', \ - q['question_id'])) for q in revisions] + q['question_id'])) for q in revisions] activities.extend(revisions) # answer revisions @@ -1462,7 +1590,7 @@ def user_recent(request, user_id, user_view): if len(revisions) > 0: revisions = [(Event(q['added_at'], q['activity_type'], q['title'], q['summary'], \ - q['answer_id'], q['question_id'])) for q in revisions] + q['answer_id'], q['question_id'])) for q in revisions] activities.extend(revisions) # accepted answers @@ -1514,12 +1642,12 @@ def user_recent(request, user_id, user_view): activities.sort(lambda x,y: cmp(y.time, x.time)) return render_to_response(user_view.template_file,{ - "tab_name" : user_view.id, - "tab_description" : user_view.tab_description, - "page_title" : user_view.page_title, - "view_user" : user, - "activities" : activities[:user_view.data_size] - }, context_instance=RequestContext(request)) + "tab_name" : user_view.id, + "tab_description" : user_view.tab_description, + "page_title" : user_view.page_title, + "view_user" : user, + "activities" : activities[:user_view.data_size] + }, context_instance=RequestContext(request)) def user_responses(request, user_id, user_view): """ @@ -1529,7 +1657,7 @@ def user_responses(request, user_id, user_view): 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 @@ -1541,30 +1669,31 @@ def user_responses(request, user_id, user_view): user = get_object_or_404(User, id=user_id) responses = [] answers = Answer.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'question.id', - 'answer_id' : 'answer.id', - 'added_at' : 'answer.added_at', - 'html' : 'answer.html', - 'username' : 'auth_user.username', - 'user_id' : 'auth_user.id' - }, - select_params=[user_id], - tables=['answer', 'question', 'auth_user'], - where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+ - 'question.author_id = %s AND answer.author_id <> %s AND answer.author_id=auth_user.id'], - params=[user_id, user_id], - order_by=['-answer.id'] - ).values( - 'title', - 'question_id', - 'answer_id', - 'added_at', - 'html', - 'username', - 'user_id' - ) + select={ + 'title' : 'question.title', + 'question_id' : 'question.id', + 'answer_id' : 'answer.id', + 'added_at' : 'answer.added_at', + 'html' : 'answer.html', + 'username' : 'auth_user.username', + 'user_id' : 'auth_user.id' + }, + select_params=[user_id], + tables=['answer', 'question', 'auth_user'], + where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+ + 'question.author_id = %s AND answer.author_id <> %s AND answer.author_id=auth_user.id'], + params=[user_id, user_id], + order_by=['-answer.id'] + ).values( + 'title', + 'question_id', + 'answer_id', + 'added_at', + 'html', + 'username', + 'user_id' + ) + if len(answers) > 0: answers = [(Response(TYPE_RESPONSE['QUESTION_ANSWERED'], a['title'], a['question_id'], a['answer_id'], a['added_at'], a['username'], a['user_id'], a['html'])) for a in answers] @@ -1573,27 +1702,27 @@ def user_responses(request, user_id, user_view): # question comments comments = Comment.objects.extra( - select={ - 'title' : 'question.title', - 'question_id' : 'comment.object_id', - 'added_at' : 'comment.added_at', - 'comment' : 'comment.comment', - 'username' : 'auth_user.username', - 'user_id' : 'auth_user.id' - }, - tables=['question', 'auth_user', 'comment'], - where=['question.deleted = 0 AND question.author_id = %s AND comment.object_id=question.id AND '+ - 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id'], - params=[user_id, question_type_id, user_id], - order_by=['-comment.added_at'] - ).values( - 'title', - 'question_id', - 'added_at', - 'comment', - 'username', - 'user_id' - ) + select={ + 'title' : 'question.title', + 'question_id' : 'comment.object_id', + 'added_at' : 'comment.added_at', + 'comment' : 'comment.comment', + 'username' : 'auth_user.username', + 'user_id' : 'auth_user.id' + }, + tables=['question', 'auth_user', 'comment'], + where=['question.deleted = 0 AND question.author_id = %s AND comment.object_id=question.id AND '+ + 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id'], + params=[user_id, question_type_id, user_id], + order_by=['-comment.added_at'] + ).values( + 'title', + 'question_id', + 'added_at', + 'comment', + 'username', + 'user_id' + ) if len(comments) > 0: comments = [(Response(TYPE_RESPONSE['QUESTION_COMMENTED'], c['title'], c['question_id'], @@ -1739,16 +1868,27 @@ def user_votes(request, user_id, user_view): def user_reputation(request, user_id, user_view): user = get_object_or_404(User, id=user_id) - reputation = Repute.objects.extra( - select={'positive': 'sum(positive)', 'negative': 'sum(negative)', 'question_id':'question_id', - 'title': 'question.title'}, - tables=['repute', 'question'], - order_by=['-reputed_at'], - where=['user_id=%s AND question_id=question.id'], - params=[user.id] - ).values('positive', 'negative', 'question_id', 'title', 'reputed_at', 'reputation') - - reputation.query.group_by = ['question_id'] + try: + from django.db.models import Sum + reputation = Repute.objects.extra( + select={'question_id':'question_id', + 'title': 'question.title'}, + tables=['repute', 'question'], + order_by=['-reputed_at'], + where=['user_id=%s AND question_id=question.id'], + params=[user.id] + ).values('question_id', 'title', 'reputed_at', 'reputation') + reputation = reputation.annotate(positive=Sum("positive"), negative=Sum("negative")) + except ImportError: + reputation = Repute.objects.extra( + select={'positive':'sum(positive)', 'negative':'sum(negative)', 'question_id':'question_id', + 'title': 'question.title'}, + tables=['repute', 'question'], + order_by=['-reputed_at'], + where=['user_id=%s AND question_id=question.id'], + params=[user.id] + ).values('positive', 'negative', 'question_id', 'title', 'reputed_at', 'reputation') + reputation.query.group_by = ['question_id'] rep_list = [] for rep in Repute.objects.filter(user=user).order_by('reputed_at'): @@ -1757,14 +1897,14 @@ def user_reputation(request, user_id, user_view): reps = ','.join(rep_list) reps = '[%s]' % reps - return render_to_response(user_view.template_file,{ - "tab_name" : user_view.id, - "tab_description" : user_view.tab_description, - "page_title" : user_view.page_title, - "view_user" : user, - "reputation" : reputation, - "reps" : reps - }, context_instance=RequestContext(request)) + return render_to_response(user_view.template_file, { + "tab_name": user_view.id, + "tab_description": user_view.tab_description, + "page_title": user_view.page_title, + "view_user": user, + "reputation": reputation, + "reps": reps + }, context_instance=RequestContext(request)) def user_favorites(request, user_id, user_view): user = get_object_or_404(User, id=user_id) @@ -1816,34 +1956,39 @@ def user_favorites(request, user_id, user_view): "view_user" : user }, context_instance=RequestContext(request)) - def user_email_subscriptions(request, user_id, user_view): user = get_object_or_404(User, id=user_id) if request.method == 'POST': - form = EditUserEmailFeedsForm(request.POST) - if form.is_valid(): + email_feeds_form = EditUserEmailFeedsForm(request.POST) + tag_filter_form = TagFilterSelectionForm(request.POST, instance=user) + if email_feeds_form.is_valid() and tag_filter_form.is_valid(): + + action_status = None + tag_filter_saved = tag_filter_form.save() + if tag_filter_saved: + action_status = _('changes saved') if 'save' in request.POST: - saved = form.save(user) - if saved: + feeds_saved = email_feeds_form.save(user) + if feeds_saved: action_status = _('changes saved') elif 'stop_email' in request.POST: - saved = form.reset().save(user) + email_stopped = email_feeds_form.reset().save(user) initial_values = EditUserEmailFeedsForm.NO_EMAIL_INITIAL - form = EditUserEmailFeedsForm(initial=initial_values) - if saved: + email_feeds_form = EditUserEmailFeedsForm(initial=initial_values) + if email_stopped: action_status = _('email updates canceled') - if not saved: - action_status = None else: - form = EditUserEmailFeedsForm() - form.set_initial_values(user) + email_feeds_form = EditUserEmailFeedsForm() + email_feeds_form.set_initial_values(user) + tag_filter_form = TagFilterSelectionForm(instance=user) action_status = None return render_to_response(user_view.template_file,{ 'tab_name':user_view.id, 'tab_description':user_view.tab_description, 'page_title':user_view.page_title, 'view_user':user, - 'email_feeds_form':form, + 'email_feeds_form':email_feeds_form, + 'tag_filter_selection_form':tag_filter_form, 'action_status':action_status, }, context_instance=RequestContext(request)) @@ -1985,7 +2130,7 @@ def upload(request): if not file_name_suffix in settings.ALLOW_FILE_TYPES: raise FileTypeNotAllow - # genetate new file name + # generate new file name new_file_name = str(time.time()).replace('.', str(random.randint(0,100000))) + file_name_suffix # use default storage to store file default_storage.save(new_file_name, f) @@ -2022,7 +2167,7 @@ def book(request, short_name, unanswered=False): """ books = Book.objects.extra(where=['short_name = %s'], params=[short_name]) match_count = len(books) - if match_count == 0 : + if match_count == 0: raise Http404 else: # the book info @@ -2193,10 +2338,16 @@ def search(request): view_id = "latest" orderby = "-added_at" - objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby) + if settings.USE_SPHINX_SEARCH == True: + #search index is now free of delete questions and answers + #so there is not "antideleted" filtering here + objects = Question.search.query(keywords) + #no related selection either because we're relying on full text search here + else: + objects = Question.objects.filter(deleted=False).extra(where=['title like %s'], params=['%' + keywords + '%']).order_by(orderby) + # RISK - inner join queries + objects = objects.select_related(); - # RISK - inner join queries - objects = objects.select_related(); objects_list = Paginator(objects, pagesize) questions = objects_list.page(page) |