diff options
Diffstat (limited to 'forum')
-rw-r--r-- | forum/feed.py | 6 | ||||
-rw-r--r-- | forum/forms.py | 54 | ||||
-rw-r--r-- | forum/forms.py.orig | 352 | ||||
-rw-r--r-- | forum/management/commands/clean_award_badges.py | 7 | ||||
-rw-r--r-- | forum/management/commands/message_to_everyone.py | 12 | ||||
-rw-r--r-- | forum/management/commands/multi_award_badges.py | 47 | ||||
-rw-r--r-- | forum/management/commands/once_award_badges.py | 23 | ||||
-rw-r--r-- | forum/management/commands/send_email_alerts.py | 106 | ||||
-rw-r--r-- | forum/management/commands/subscribe_everyone.py | 9 | ||||
-rw-r--r-- | forum/managers.py | 15 | ||||
-rw-r--r-- | forum/models.py | 19 | ||||
-rw-r--r-- | forum/sitemap.py | 3 | ||||
-rw-r--r-- | forum/templatetags/extra_tags.py | 7 | ||||
-rw-r--r-- | forum/templatetags/smart_if.py | 802 | ||||
-rw-r--r-- | forum/urls.py | 3 | ||||
-rw-r--r-- | forum/views.py | 76 |
16 files changed, 948 insertions, 593 deletions
diff --git a/forum/feed.py b/forum/feed.py index ad1d5cbd..e4b929e9 100644 --- a/forum/feed.py +++ b/forum/feed.py @@ -13,16 +13,16 @@ from django.contrib.syndication.feeds import Feed, FeedDoesNotExist from django.utils.translation import ugettext as _ from models import Question -import settings +from django.conf import settings class RssLastestQuestionsFeed(Feed): title = settings.APP_TITLE + _(' - ')+ _('latest questions') - link = settings.APP_URL + '/' + _('question/') + link = settings.APP_URL #+ '/' + _('question/') description = settings.APP_DESCRIPTION #ttl = 10 copyright = settings.APP_COPYRIGHT def item_link(self, item): - return self.link + '%s/' % item.id + return self.link + item.get_absolute_url() def item_author_name(self, item): return item.author.username diff --git a/forum/forms.py b/forum/forms.py index ad2c5bac..308f853b 100644 --- a/forum/forms.py +++ b/forum/forms.py @@ -4,8 +4,10 @@ from django import forms from models import * from const import * from django.utils.translation import ugettext as _ -from django_authopenid.forms import NextUrlField, UserNameField -import settings +from utils.forms import NextUrlField, UserNameField +from recaptcha_django import ReCaptchaField +from django.conf import settings +import logging class TitleField(forms.CharField): def __init__(self, *args, **kwargs): @@ -109,6 +111,9 @@ class ModerateUserForm(forms.ModelForm): model = User fields = ('is_approved',) +class NotARobotForm(forms.Form): + recaptcha = ReCaptchaField() + class FeedbackForm(forms.Form): name = forms.CharField(label=_('Your name:'), required=False) email = forms.EmailField(label=_('Email (not shared with anyone):'), required=False) @@ -194,8 +199,9 @@ class EditAnswerForm(forms.Form): self.fields['text'].initial = revision.text 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')) + email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=True, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + if settings.EDITABLE_SCREEN_NAME: + 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})) @@ -204,7 +210,10 @@ class EditUserForm(forms.Form): def __init__(self, user, *args, **kwargs): super(EditUserForm, self).__init__(*args, **kwargs) - self.fields['username'].initial = user.username + logging.debug('initializing the form') + if settings.EDITABLE_SCREEN_NAME: + self.fields['username'].initial = user.username + self.fields['username'].user_instance = user self.fields['email'].initial = user.email self.fields['realname'].initial = user.real_name self.fields['website'].initial = user.website @@ -298,14 +307,24 @@ class EditUserEmailFeedsForm(forms.Form): self.initial = self.NO_EMAIL_INITIAL return self - def save(self,user): + def save(self,user,save_unbound=False): + """ + with save_unbound==True will bypass form validation and save initial values + """ changed = False for form_field, feed_type in self.FORM_TO_MODEL_MAP.items(): s, created = EmailFeedSetting.objects.get_or_create(subscriber=user,\ feed_type=feed_type) - new_value = self.cleaned_data[form_field] + if save_unbound: + #just save initial values instead + if form_field in self.initial: + new_value = self.initial[form_field] + else: + new_value = self.fields[form_field].initial + else: + new_value = self.cleaned_data[form_field] if s.frequency != new_value: - s.frequency = self.cleaned_data[form_field] + s.frequency = new_value s.save() changed = True else: @@ -315,3 +334,22 @@ class EditUserEmailFeedsForm(forms.Form): feed_type = ContentType.objects.get_for_model(Question) user.followed_questions.clear() return changed + + +class SimpleEmailSubscribeForm(forms.Form): + SIMPLE_SUBSCRIBE_CHOICES = ( + ('y',_('okay, let\'s try!')), + ('n',_('no OSQA community email please, thanks')) + ) + subscribe = forms.ChoiceField(widget=forms.widgets.RadioSelect(), \ + error_messages={'required':_('please choose one of the options above')}, + choices=SIMPLE_SUBSCRIBE_CHOICES) + + def save(self,user=None): + EFF = EditUserEmailFeedsForm + if self.cleaned_data['subscribe'] == 'y': + email_settings_form = EFF() + logging.debug('%s wants to subscribe' % user.username) + else: + email_settings_form = EFF(initial=EFF.NO_EMAIL_INITIAL) + email_settings_form.save(user,save_unbound=True) diff --git a/forum/forms.py.orig b/forum/forms.py.orig new file mode 100644 index 00000000..42becc11 --- /dev/null +++ b/forum/forms.py.orig @@ -0,0 +1,352 @@ +import re +from datetime import date +from django import forms +from models import * +from const import * +from django.utils.translation import ugettext as _ +from utils.forms import NextUrlField, UserNameField +from recaptcha_django import ReCaptchaField +from django.conf import settings +import logging + +class TitleField(forms.CharField): + def __init__(self, *args, **kwargs): + super(TitleField, self).__init__(*args, **kwargs) + self.required = True + self.widget = forms.TextInput(attrs={'size' : 70, 'autocomplete' : 'off'}) + self.max_length = 255 + self.label = _('title') + self.help_text = _('please enter a descriptive title for your question') + self.initial = '' + + def clean(self, value): + if len(value) < 10: + raise forms.ValidationError(_('title must be > 10 characters')) + + return value + +class EditorField(forms.CharField): + def __init__(self, *args, **kwargs): + super(EditorField, self).__init__(*args, **kwargs) + self.required = True + self.widget = forms.Textarea(attrs={'id':'editor'}) + self.label = _('content') + self.help_text = u'' + self.initial = '' + + def clean(self, value): + if len(value) < 10: + raise forms.ValidationError(_('question content must be > 10 characters')) + + return value + +class TagNamesField(forms.CharField): + def __init__(self, *args, **kwargs): + super(TagNamesField, self).__init__(*args, **kwargs) + self.required = True + self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'}) + self.max_length = 255 + self.label = _('tags') + #self.help_text = _('please use space to separate tags (this enables autocomplete feature)') + self.help_text = _('Tags are short keywords, with no spaces within. Up to five tags can be used.') + self.initial = '' + + def clean(self, value): + value = super(TagNamesField, self).clean(value) + data = value.strip() + if len(data) < 1: + raise forms.ValidationError(_('tags are required')) + + split_re = re.compile(r'[ ,]+') + list = split_re.split(data) + list_temp = [] + if len(list) > 5: + raise forms.ValidationError(_('please use 5 tags or less')) + for tag in list: + if len(tag) > 20: + raise forms.ValidationError(_('tags must be shorter than 20 characters')) + #take tag regex from settings + tagname_re = re.compile(r'[a-z0-9]+') + if not tagname_re.match(tag): + raise forms.ValidationError(_('please use following characters in tags: letters \'a-z\', numbers, and characters \'.-_#\'')) + # only keep one same tag + if tag not in list_temp and len(tag.strip()) > 0: + list_temp.append(tag) + return u' '.join(list_temp) + +class WikiField(forms.BooleanField): + def __init__(self, *args, **kwargs): + super(WikiField, self).__init__(*args, **kwargs) + self.required = False + self.label = _('community wiki') + self.help_text = _('if you choose community wiki option, the question and answer do not generate points and name of author will not be shown') + def clean(self,value): + return value and settings.WIKI_ON + +class EmailNotifyField(forms.BooleanField): + def __init__(self, *args, **kwargs): + super(EmailNotifyField, self).__init__(*args, **kwargs) + self.required = False + self.widget.attrs['class'] = 'nomargin' + +class SummaryField(forms.CharField): + def __init__(self, *args, **kwargs): + super(SummaryField, self).__init__(*args, **kwargs) + self.required = False + self.widget = forms.TextInput(attrs={'size' : 50, 'autocomplete' : 'off'}) + self.max_length = 300 + self.label = _('update summary:') + self.help_text = _('enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)') + +class ModerateUserForm(forms.ModelForm): + is_approved = forms.BooleanField(label=_("Automatically accept user's contributions for the email updates"), + required=False) + + def clean_is_approved(self): + if 'is_approved' not in self.cleaned_data: + self.cleaned_data['is_approved'] = False + return self.cleaned_data['is_approved'] + + class Meta: + model = User + fields = ('is_approved',) + +class NotARobotForm(forms.Form): + recaptcha = ReCaptchaField() + +class FeedbackForm(forms.Form): + name = forms.CharField(label=_('Your name:'), required=False) + email = forms.EmailField(label=_('Email (not shared with anyone):'), required=False) + message = forms.CharField(label=_('Your message:'), max_length=800,widget=forms.Textarea(attrs={'cols':60})) + next = NextUrlField() + +class AskForm(forms.Form): + title = TitleField() + text = EditorField() + tags = TagNamesField() + wiki = WikiField() + + openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'})) + user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + +class AnswerForm(forms.Form): + text = EditorField() + wiki = WikiField() + openid = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 40, 'class':'openid-input'})) + user = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + email = forms.CharField(required=False, max_length=255, widget=forms.TextInput(attrs={'size' : 35})) + email_notify = EmailNotifyField() + def __init__(self, question, user, *args, **kwargs): + super(AnswerForm, self).__init__(*args, **kwargs) + self.fields['email_notify'].widget.attrs['id'] = 'question-subscribe-updates'; + if question.wiki and settings.WIKI_ON: + self.fields['wiki'].initial = True + if user.is_authenticated(): + if user in question.followed_by.all(): + self.fields['email_notify'].initial = True + return + self.fields['email_notify'].initial = False + + +class CloseForm(forms.Form): + reason = forms.ChoiceField(choices=CLOSE_REASONS) + +class RetagQuestionForm(forms.Form): + tags = TagNamesField() + # initialize the default values + def __init__(self, question, *args, **kwargs): + super(RetagQuestionForm, self).__init__(*args, **kwargs) + self.fields['tags'].initial = question.tagnames + +class RevisionForm(forms.Form): + """ + Lists revisions of a Question or Answer + """ + revision = forms.ChoiceField(widget=forms.Select(attrs={'style' : 'width:520px'})) + + def __init__(self, post, latest_revision, *args, **kwargs): + super(RevisionForm, self).__init__(*args, **kwargs) + revisions = post.revisions.all().values_list( + 'revision', 'author__username', 'revised_at', 'summary') + date_format = '%c' + self.fields['revision'].choices = [ + (r[0], u'%s - %s (%s) %s' % (r[0], r[1], r[2].strftime(date_format), r[3])) + for r in revisions] + self.fields['revision'].initial = latest_revision.revision + +class EditQuestionForm(forms.Form): + title = TitleField() + text = EditorField() + tags = TagNamesField() + summary = SummaryField() + + def __init__(self, question, revision, *args, **kwargs): + super(EditQuestionForm, self).__init__(*args, **kwargs) + self.fields['title'].initial = revision.title + self.fields['text'].initial = revision.text + self.fields['tags'].initial = revision.tagnames + # Once wiki mode is enabled, it can't be disabled + if not question.wiki: + self.fields['wiki'] = WikiField() + +class EditAnswerForm(forms.Form): + text = EditorField() + summary = SummaryField() + + def __init__(self, answer, revision, *args, **kwargs): + super(EditAnswerForm, self).__init__(*args, **kwargs) + self.fields['text'].initial = revision.text + +class EditUserForm(forms.Form): + email = forms.EmailField(label=u'Email', help_text=_('this email does not have to be linked to gravatar'), required=True, 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})) + birthday = forms.DateField(label=_('Date of birth'), help_text=_('will not be shown, used to calculate age, format: YYYY-MM-DD'), required=False, widget=forms.TextInput(attrs={'size' : 35})) + about = forms.CharField(label=_('Profile'), required=False, widget=forms.Textarea(attrs={'cols' : 60})) + + def __init__(self, user, *args, **kwargs): + super(EditUserForm, self).__init__(*args, **kwargs) + self.fields['username'].initial = user.username + self.fields['username'].user_instance = user + self.fields['email'].initial = user.email + self.fields['realname'].initial = user.real_name + self.fields['website'].initial = user.website + self.fields['city'].initial = user.location + + if user.date_of_birth is not None: + self.fields['birthday'].initial = user.date_of_birth + else: + self.fields['birthday'].initial = '1990-01-01' + self.fields['about'].initial = user.about + self.user = user + + def clean_email(self): + """For security reason one unique email in database""" + if self.user.email != self.cleaned_data['email']: + #todo dry it, there is a similar thing in openidauth + if settings.EMAIL_UNIQUE == True: + if 'email' in self.cleaned_data: + try: + user = User.objects.get(email = self.cleaned_data['email']) + except User.DoesNotExist: + return self.cleaned_data['email'] + except User.MultipleObjectsReturned: + raise forms.ValidationError(_('this email has already been registered, please use another one')) + 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'))) + FORM_TO_MODEL_MAP = { + 'all_questions':'q_all', + 'asked_by_me':'q_ask', + 'answered_by_me':'q_ans', + 'individually_selected':'q_sel', + } + NO_EMAIL_INITIAL = { + 'all_questions':'n', + 'asked_by_me':'n', + 'answered_by_me':'n', + 'individually_selected':'n', + } + asked_by_me = forms.ChoiceField(choices=DWN,initial='w', + widget=forms.RadioSelect, + label=_('Asked by me')) + answered_by_me = forms.ChoiceField(choices=DWN,initial='w', + widget=forms.RadioSelect, + label=_('Answered by me')) + 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()]) + if user != None: + settings = EmailFeedSetting.objects.filter(subscriber=user) + initial_values = {} + for setting in settings: + feed_type = setting.feed_type + form_field = KEY_MAP[feed_type] + frequency = setting.frequency + initial_values[form_field] = frequency + self.initial = initial_values + return self + + def reset(self): + self.cleaned_data['all_questions'] = 'n' + self.cleaned_data['asked_by_me'] = 'n' + self.cleaned_data['answered_by_me'] = 'n' + self.cleaned_data['individually_selected'] = 'n' + self.initial = self.NO_EMAIL_INITIAL + return self + + def save(self,user,save_unbound=False): + """ + with save_unbound==True will bypass form validation and save initial values + """ + changed = False + for form_field, feed_type in self.FORM_TO_MODEL_MAP.items(): + s, created = EmailFeedSetting.objects.get_or_create(subscriber=user,\ + feed_type=feed_type) + if save_unbound: + #just save initial values instead + if form_field in self.initial: + new_value = self.initial[form_field] + else: + new_value = self.fields[form_field].initial + else: + new_value = self.cleaned_data[form_field] + if s.frequency != new_value: + s.frequency = new_value + s.save() + changed = True + else: + if created: + s.save() + if form_field == 'individually_selected': + feed_type = ContentType.objects.get_for_model(Question) + user.followed_questions.clear() + return changed + + +class SimpleEmailSubscribeForm(forms.Form): + SIMPLE_SUBSCRIBE_CHOICES = ( + ('y',_('okay, let\'s try!')), + ('n',_('no OSQA community email please, thanks')) + ) + subscribe = forms.ChoiceField(widget=forms.widgets.RadioSelect(), \ + error_messages={'required':_('please choose one of the options above')}, + choices=SIMPLE_SUBSCRIBE_CHOICES) + + def save(self,user=None): + EFF = EditUserEmailFeedsForm + if self.cleaned_data['subscribe'] == 'y': + email_settings_form = EFF() + logging.debug('%s wants to subscribe' % user.username) + else: + email_settings_form = EFF(initial=EFF.NO_EMAIL_INITIAL) + email_settings_form.save(user,save_unbound=True) diff --git a/forum/management/commands/clean_award_badges.py b/forum/management/commands/clean_award_badges.py index df3d2917..117e3a5f 100644 --- a/forum/management/commands/clean_award_badges.py +++ b/forum/management/commands/clean_award_badges.py @@ -21,9 +21,10 @@ from forum.models import * class Command(NoArgsCommand): def handle_noargs(self, **options): try: - self.clean_awards() - except Exception, e: - print e + try: + self.clean_awards() + except Exception, e: + print e finally: connection.close() diff --git a/forum/management/commands/message_to_everyone.py b/forum/management/commands/message_to_everyone.py new file mode 100644 index 00000000..c020c178 --- /dev/null +++ b/forum/management/commands/message_to_everyone.py @@ -0,0 +1,12 @@ +from django.core.management.base import NoArgsCommand +from django.contrib.auth.models import User +import sys + +class Command(NoArgsCommand): + def handle_noargs(self, **options): + msg = None + if msg == None: + print 'to run this command, please first edit the file %s' % __file__ + sys.exit(1) + for u in User.objects.all(): + u.message_set.create(message = msg % u.username) diff --git a/forum/management/commands/multi_award_badges.py b/forum/management/commands/multi_award_badges.py index 723a8cec..6b330cf9 100644 --- a/forum/management/commands/multi_award_badges.py +++ b/forum/management/commands/multi_award_badges.py @@ -82,27 +82,28 @@ TYPE_ACTIVITY_USER_FULL_UPDATED = 17 class Command(BaseCommand): def handle_noargs(self, **options): try: - self.delete_question_be_voted_up_3() - self.delete_answer_be_voted_up_3() - self.delete_question_be_vote_down_3() - self.delete_answer_be_voted_down_3() - self.answer_be_voted_up_10() - self.question_be_voted_up_10() - self.question_view_1000() - self.answer_self_question_be_voted_up_3() - self.answer_be_voted_up_100() - self.question_be_voted_up_100() - self.question_be_favorited_100() - self.question_view_10000() - self.answer_be_voted_up_25() - self.question_be_voted_up_25() - self.question_be_favorited_25() - self.question_view_2500() - self.answer_be_accepted_and_voted_up_40() - self.question_be_answered_after_60_days_and_be_voted_up_5() - self.created_tag_be_used_in_question_50() - except Exception, e: - print e + try: + self.delete_question_be_voted_up_3() + self.delete_answer_be_voted_up_3() + self.delete_question_be_vote_down_3() + self.delete_answer_be_voted_down_3() + self.answer_be_voted_up_10() + self.question_be_voted_up_10() + self.question_view_1000() + self.answer_self_question_be_voted_up_3() + self.answer_be_voted_up_100() + self.question_be_voted_up_100() + self.question_be_favorited_100() + self.question_view_10000() + self.answer_be_voted_up_25() + self.question_be_voted_up_25() + self.question_be_favorited_25() + self.question_view_2500() + self.answer_be_accepted_and_voted_up_40() + self.question_be_answered_after_60_days_and_be_voted_up_5() + self.created_tag_be_used_in_question_50() + except Exception, e: + print e finally: connection.close() @@ -317,7 +318,7 @@ class Command(BaseCommand): object_id = row[2] user = get_object_or_404(User, id=user_id) - award = Award(user=user, badge=badge, content_type=content_type, object_id=objet_id) + award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) award.save() if update_auditted: @@ -344,4 +345,4 @@ class Command(BaseCommand): award = Award(user=user, badge=badge, content_type=content_type, object_id=object_id) award.save() finally: - cursor.close()
\ No newline at end of file + cursor.close() diff --git a/forum/management/commands/once_award_badges.py b/forum/management/commands/once_award_badges.py index 03982c79..8c913348 100644 --- a/forum/management/commands/once_award_badges.py +++ b/forum/management/commands/once_award_badges.py @@ -94,17 +94,18 @@ BADGE_AWARD_TYPE_FIRST = { class Command(BaseCommand): def handle_noargs(self, **options): try: - self.alpha_user() - self.beta_user() - self.first_type_award() - self.first_ask_be_voted() - self.first_answer_be_voted() - self.first_answer_be_voted_10() - self.vote_count_300() - self.edit_count_100() - self.comment_count_10() - except Exception, e: - print e + try: + self.alpha_user() + self.beta_user() + self.first_type_award() + self.first_ask_be_voted() + self.first_answer_be_voted() + self.first_answer_be_voted_10() + self.vote_count_300() + self.edit_count_100() + self.comment_count_10() + except Exception, e: + print e finally: connection.close() diff --git a/forum/management/commands/send_email_alerts.py b/forum/management/commands/send_email_alerts.py index 283d5683..5e1eb3d0 100644 --- a/forum/management/commands/send_email_alerts.py +++ b/forum/management/commands/send_email_alerts.py @@ -2,42 +2,30 @@ 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 -======= +from django.conf import settings import logging from utils.odict import OrderedDict ->>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/management/commands/send_email_alerts.py class Command(NoArgsCommand): def handle_noargs(self,**options): try: - self.send_email_alerts() - except Exception, e: - print e + try: + self.send_email_alerts() + except Exception, e: + print e finally: 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, @@ -51,28 +39,17 @@ 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 @@ -80,58 +57,12 @@ 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 - q_tbl = {} - def extend_question_list(src, dst): - if isinstance(src,list): - return - cutoff_time = src.cutoff_time - for q in src: - if q in dst: - if cutoff_time < dst[q]: - dst[q] = cutoff_time - else: - dst[q] = 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) - - ctype = ContentType.objects.get_for_model(Question) - out = {} - for q, cutoff_time in q_tbl.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) - q_rev = q_rev.exclude(author=user) - out[q]['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 - else: - out[q]['new_q'] = False - - new_ans = Answer.objects.filter(question=q,added_at__lt=cutoff_time) - 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) - ans_rev = ans_rev.exclude(author=user) - out[q]['ans_rev'] = len(ans_rev) - 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) + 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) + 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 @@ -206,17 +137,10 @@ class Command(NoArgsCommand): 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) @@ -227,28 +151,15 @@ class Command(NoArgsCommand): 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') + 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>' -<<<<<<< HEAD:forum/management/commands/send_email_alerts.py - for q, act 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) - text += '</ul>' -======= for q, meta_data in q_list.items(): act_list = [] if meta_data['nothing_new']: @@ -273,7 +184,6 @@ class Command(NoArgsCommand): 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/management/commands/subscribe_everyone.py b/forum/management/commands/subscribe_everyone.py index 3f8da9ec..c79528f3 100644 --- a/forum/management/commands/subscribe_everyone.py +++ b/forum/management/commands/subscribe_everyone.py @@ -6,14 +6,15 @@ from django.core.mail import EmailMessage from django.utils.translation import ugettext as _ from django.utils.translation import ungettext import datetime -import settings +from django.conf import settings class Command(NoArgsCommand): def handle_noargs(self,**options): try: - self.subscribe_everyone() - except Exception, e: - print e + try: + self.subscribe_everyone() + except Exception, e: + print e finally: connection.close() diff --git a/forum/managers.py b/forum/managers.py index 06fae761..9079e9a6 100644 --- a/forum/managers.py +++ b/forum/managers.py @@ -1,4 +1,5 @@ import datetime +import time import logging from django.contrib.auth.models import User, UserManager from django.db import connection, models, transaction @@ -122,7 +123,7 @@ class TagManager(models.Manager): 'SET used_count = (' 'SELECT COUNT(*) FROM question_tags ' 'INNER JOIN question ON question_id=question.id ' - 'WHERE tag_id = tag.id AND question.deleted=0' + 'WHERE tag_id = tag.id AND question.deleted=False' ') ' 'WHERE id IN (%s)') @@ -200,7 +201,7 @@ class AnswerManager(models.Manager): class VoteManager(models.Manager): COUNT_UP_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = 1" COUNT_DOWN_VOTE_BY_USER = "SELECT count(*) FROM vote WHERE user_id = %s AND vote = -1" - COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = DATE(NOW())" + COUNT_VOTES_PER_DAY_BY_USER = "SELECT COUNT(*) FROM vote WHERE user_id = %s AND DATE(voted_at) = %s" def get_up_vote_count_from_user(self, user): if user is not None: cursor = connection.cursor() @@ -222,7 +223,7 @@ class VoteManager(models.Manager): def get_votes_count_today_from_user(self, user): if user is not None: cursor = connection.cursor() - cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id]) + cursor.execute(self.COUNT_VOTES_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())]) row = cursor.fetchone() return row[0] @@ -230,11 +231,11 @@ class VoteManager(models.Manager): return 0 class FlaggedItemManager(models.Manager): - COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = DATE(NOW())" + COUNT_FLAGS_PER_DAY_BY_USER = "SELECT COUNT(*) FROM flagged_item WHERE user_id = %s AND DATE(flagged_at) = %s" def get_flagged_items_count_today(self, user): if user is not None: cursor = connection.cursor() - cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id]) + cursor.execute(self.COUNT_FLAGS_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())]) row = cursor.fetchone() return row[0] @@ -242,7 +243,7 @@ class FlaggedItemManager(models.Manager): return 0 class ReputeManager(models.Manager): - COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = DATE(NOW())" + COUNT_REPUTATION_PER_DAY_BY_USER = "SELECT SUM(positive)+SUM(negative) FROM repute WHERE user_id = %s AND (reputation_type=1 OR reputation_type=-8) AND DATE(reputed_at) = %s" def get_reputation_by_upvoted_today(self, user): """ For one user in one day, he can only earn rep till certain score (ep. +200) @@ -251,7 +252,7 @@ class ReputeManager(models.Manager): """ if user is not None: cursor = connection.cursor() - cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id]) + cursor.execute(self.COUNT_REPUTATION_PER_DAY_BY_USER, [user.id, time.strftime("%Y-%m-%d", datetime.datetime.now().timetuple())]) row = cursor.fetchone() return row[0] diff --git a/forum/models.py b/forum/models.py index f1876588..fe2121b5 100644 --- a/forum/models.py +++ b/forum/models.py @@ -22,7 +22,7 @@ import settings ======= from django.contrib.sitemaps import ping_google import django.dispatch -import settings +from django.conf import settings import logging if settings.USE_SPHINX_SEARCH == True: @@ -415,7 +415,7 @@ class QuestionRevision(models.Model): return self.question.title def get_absolute_url(self): - print 'in QuestionRevision.get_absolute_url()' + #print 'in QuestionRevision.get_absolute_url()' return reverse('question_revisions', args=[self.question.id]) def save(self, **kwargs): @@ -442,7 +442,7 @@ class AnonymousAnswer(models.Model): def publish(self,user): from forum.views import create_new_answer added_at = datetime.datetime.now() - print user.id + #print user.id create_new_answer(question=self.question,wiki=self.wiki, added_at=added_at,text=self.text, author=user) @@ -510,8 +510,11 @@ class Answer(models.Model): >>>>>>> 82d35490db90878f013523c4d1a5ec3af2df8b23:forum/models.py def get_user_vote(self, user): + if user.__class__.__name__ == "AnonymousUser": + return None + votes = self.votes.filter(user=user) - if votes.count() > 0: + if votes and votes.count() > 0: return votes[0] else: return None @@ -729,13 +732,13 @@ def user_is_username_taken(cls,username): return False def user_get_q_sel_email_feed_frequency(self): - print 'looking for frequency for user %s' % self + #print 'looking for frequency for user %s' % self try: feed_setting = EmailFeedSetting.objects.get(subscriber=self,feed_type='q_sel') except Exception, e: - print 'have error %s' % e.message + #print 'have error %s' % e.message raise e - print 'have freq=%s' % feed_setting.frequency + #print 'have freq=%s' % feed_setting.frequency return feed_setting.frequency User.add_to_class('is_approved', models.BooleanField(default=False)) @@ -871,7 +874,7 @@ def notify_award_message(instance, created, **kwargs): """ if created: user = instance.user - user.message_set.create(message=u"%s" % instance.badge.name) + user.message_set.create(message=u"Congratulations, you have received a badge '%s'" % instance.badge.name) def record_answer_accepted(instance, created, **kwargs): """ diff --git a/forum/sitemap.py b/forum/sitemap.py index dc97a009..c0c60b5e 100644 --- a/forum/sitemap.py +++ b/forum/sitemap.py @@ -9,3 +9,6 @@ class QuestionsSitemap(Sitemap): def lastmod(self, obj): return obj.last_activity_at + + def location(self, obj): + return obj.get_absolute_url() diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py index 8ed79d3c..29fdd831 100644 --- a/forum/templatetags/extra_tags.py +++ b/forum/templatetags/extra_tags.py @@ -1,5 +1,6 @@ import time import os +import posixpath import datetime import math import re @@ -244,9 +245,9 @@ def diff_date(date, limen=2): if days > 2: if date.year == now.year: - return date.strftime(_("%b %d at %H:%M")) + return date.strftime("%b %d at %H:%M") else: - return date.strftime(_("%b %d '%y at %H:%M")) + return date.strftime("%b %d '%y at %H:%M") elif days == 2: return _('2 days ago') elif days == 1: @@ -281,7 +282,7 @@ def get_latest_changed_timestamp(): @register.simple_tag def href(url): url = '///' + settings.FORUM_SCRIPT_ALIAS + '/' + url - return os.path.normpath(url) + '?v=%d' % settings.RESOURCE_REVISION + return posixpath.normpath(url) + '?v=%d' % settings.RESOURCE_REVISION class ItemSeparatorNode(template.Node): def __init__(self,separator): diff --git a/forum/templatetags/smart_if.py b/forum/templatetags/smart_if.py index a8fc1944..ca3b43fe 100644 --- a/forum/templatetags/smart_if.py +++ b/forum/templatetags/smart_if.py @@ -1,401 +1,401 @@ -"""
-A smarter {% if %} tag for django templates.
-
-While retaining current Django functionality, it also handles equality,
-greater than and less than operators. Some common case examples::
-
- {% if articles|length >= 5 %}...{% endif %}
- {% if "ifnotequal tag" != "beautiful" %}...{% endif %}
-"""
-import unittest
-from django import template
-
-
-register = template.Library()
-
-
-#==============================================================================
-# Calculation objects
-#==============================================================================
-
-class BaseCalc(object):
- def __init__(self, var1, var2=None, negate=False):
- self.var1 = var1
- self.var2 = var2
- self.negate = negate
-
- def resolve(self, context):
- try:
- var1, var2 = self.resolve_vars(context)
- outcome = self.calculate(var1, var2)
- except:
- outcome = False
- if self.negate:
- return not outcome
- return outcome
-
- def resolve_vars(self, context):
- var2 = self.var2 and self.var2.resolve(context)
- return self.var1.resolve(context), var2
-
- def calculate(self, var1, var2):
- raise NotImplementedError()
-
-
-class Or(BaseCalc):
- def calculate(self, var1, var2):
- return var1 or var2
-
-
-class And(BaseCalc):
- def calculate(self, var1, var2):
- return var1 and var2
-
-
-class Equals(BaseCalc):
- def calculate(self, var1, var2):
- return var1 == var2
-
-
-class Greater(BaseCalc):
- def calculate(self, var1, var2):
- return var1 > var2
-
-
-class GreaterOrEqual(BaseCalc):
- def calculate(self, var1, var2):
- return var1 >= var2
-
-
-class In(BaseCalc):
- def calculate(self, var1, var2):
- return var1 in var2
-
-
-#==============================================================================
-# Tests
-#==============================================================================
-
-class TestVar(object):
- """
- A basic self-resolvable object similar to a Django template variable. Used
- to assist with tests.
- """
- def __init__(self, value):
- self.value = value
-
- def resolve(self, context):
- return self.value
-
-
-class SmartIfTests(unittest.TestCase):
- def setUp(self):
- self.true = TestVar(True)
- self.false = TestVar(False)
- self.high = TestVar(9000)
- self.low = TestVar(1)
-
- def assertCalc(self, calc, context=None):
- """
- Test a calculation is True, also checking the inverse "negate" case.
- """
- context = context or {}
- self.assert_(calc.resolve(context))
- calc.negate = not calc.negate
- self.assertFalse(calc.resolve(context))
-
- def assertCalcFalse(self, calc, context=None):
- """
- Test a calculation is False, also checking the inverse "negate" case.
- """
- context = context or {}
- self.assertFalse(calc.resolve(context))
- calc.negate = not calc.negate
- self.assert_(calc.resolve(context))
-
- def test_or(self):
- self.assertCalc(Or(self.true))
- self.assertCalcFalse(Or(self.false))
- self.assertCalc(Or(self.true, self.true))
- self.assertCalc(Or(self.true, self.false))
- self.assertCalc(Or(self.false, self.true))
- self.assertCalcFalse(Or(self.false, self.false))
-
- def test_and(self):
- self.assertCalc(And(self.true, self.true))
- self.assertCalcFalse(And(self.true, self.false))
- self.assertCalcFalse(And(self.false, self.true))
- self.assertCalcFalse(And(self.false, self.false))
-
- def test_equals(self):
- self.assertCalc(Equals(self.low, self.low))
- self.assertCalcFalse(Equals(self.low, self.high))
-
- def test_greater(self):
- self.assertCalc(Greater(self.high, self.low))
- self.assertCalcFalse(Greater(self.low, self.low))
- self.assertCalcFalse(Greater(self.low, self.high))
-
- def test_greater_or_equal(self):
- self.assertCalc(GreaterOrEqual(self.high, self.low))
- self.assertCalc(GreaterOrEqual(self.low, self.low))
- self.assertCalcFalse(GreaterOrEqual(self.low, self.high))
-
- def test_in(self):
- list_ = TestVar([1,2,3])
- invalid_list = TestVar(None)
- self.assertCalc(In(self.low, list_))
- self.assertCalcFalse(In(self.low, invalid_list))
-
- def test_parse_bits(self):
- var = IfParser([True]).parse()
- self.assert_(var.resolve({}))
- var = IfParser([False]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser([False, 'or', True]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([False, 'and', True]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser(['not', False, 'and', 'not', False]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser(['not', 'not', True]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([1, '=', 1]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([1, 'not', '=', 1]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser([1, 'not', 'not', '=', 1]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([1, '!=', 1]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser([3, '>', 2]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([1, '<', 2]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([2, 'not', 'in', [2, 3]]).parse()
- self.assertFalse(var.resolve({}))
-
- var = IfParser([1, 'or', 1, '=', 2]).parse()
- self.assert_(var.resolve({}))
-
- def test_boolean(self):
- var = IfParser([True, 'and', True, 'and', True]).parse()
- self.assert_(var.resolve({}))
- var = IfParser([False, 'or', False, 'or', True]).parse()
- self.assert_(var.resolve({}))
- var = IfParser([True, 'and', False, 'or', True]).parse()
- self.assert_(var.resolve({}))
- var = IfParser([False, 'or', True, 'and', True]).parse()
- self.assert_(var.resolve({}))
-
- var = IfParser([True, 'and', True, 'and', False]).parse()
- self.assertFalse(var.resolve({}))
- var = IfParser([False, 'or', False, 'or', False]).parse()
- self.assertFalse(var.resolve({}))
- var = IfParser([False, 'or', True, 'and', False]).parse()
- self.assertFalse(var.resolve({}))
- var = IfParser([False, 'and', True, 'or', False]).parse()
- self.assertFalse(var.resolve({}))
-
- def test_invalid(self):
- self.assertRaises(ValueError, IfParser(['not']).parse)
- self.assertRaises(ValueError, IfParser(['==']).parse)
- self.assertRaises(ValueError, IfParser([1, 'in']).parse)
- self.assertRaises(ValueError, IfParser([1, '>', 'in']).parse)
- self.assertRaises(ValueError, IfParser([1, '==', 'not', 'not']).parse)
- self.assertRaises(ValueError, IfParser([1, 2]).parse)
-
-
-OPERATORS = {
- '=': (Equals, True),
- '==': (Equals, True),
- '!=': (Equals, False),
- '>': (Greater, True),
- '>=': (GreaterOrEqual, True),
- '<=': (Greater, False),
- '<': (GreaterOrEqual, False),
- 'or': (Or, True),
- 'and': (And, True),
- 'in': (In, True),
-}
-BOOL_OPERATORS = ('or', 'and')
-
-
-class IfParser(object):
- error_class = ValueError
-
- def __init__(self, tokens):
- self.tokens = tokens
-
- def _get_tokens(self):
- return self._tokens
-
- def _set_tokens(self, tokens):
- self._tokens = tokens
- self.len = len(tokens)
- self.pos = 0
-
- tokens = property(_get_tokens, _set_tokens)
-
- def parse(self):
- if self.at_end():
- raise self.error_class('No variables provided.')
- var1 = self.get_bool_var()
- while not self.at_end():
- op, negate = self.get_operator()
- var2 = self.get_bool_var()
- var1 = op(var1, var2, negate=negate)
- return var1
-
- def get_token(self, eof_message=None, lookahead=False):
- negate = True
- token = None
- pos = self.pos
- while token is None or token == 'not':
- if pos >= self.len:
- if eof_message is None:
- raise self.error_class()
- raise self.error_class(eof_message)
- token = self.tokens[pos]
- negate = not negate
- pos += 1
- if not lookahead:
- self.pos = pos
- return token, negate
-
- def at_end(self):
- return self.pos >= self.len
-
- def create_var(self, value):
- return TestVar(value)
-
- def get_bool_var(self):
- """
- Returns either a variable by itself or a non-boolean operation (such as
- ``x == 0`` or ``x < 0``).
-
- This is needed to keep correct precedence for boolean operations (i.e.
- ``x or x == 0`` should be ``x or (x == 0)``, not ``(x or x) == 0``).
- """
- var = self.get_var()
- if not self.at_end():
- op_token = self.get_token(lookahead=True)[0]
- if isinstance(op_token, basestring) and (op_token not in
- BOOL_OPERATORS):
- op, negate = self.get_operator()
- return op(var, self.get_var(), negate=negate)
- return var
-
- def get_var(self):
- token, negate = self.get_token('Reached end of statement, still '
- 'expecting a variable.')
- if isinstance(token, basestring) and token in OPERATORS:
- raise self.error_class('Expected variable, got operator (%s).' %
- token)
- var = self.create_var(token)
- if negate:
- return Or(var, negate=True)
- return var
-
- def get_operator(self):
- token, negate = self.get_token('Reached end of statement, still '
- 'expecting an operator.')
- if not isinstance(token, basestring) or token not in OPERATORS:
- raise self.error_class('%s is not a valid operator.' % token)
- if self.at_end():
- raise self.error_class('No variable provided after "%s".' % token)
- op, true = OPERATORS[token]
- if not true:
- negate = not negate
- return op, negate
-
-
-#==============================================================================
-# Actual templatetag code.
-#==============================================================================
-
-class TemplateIfParser(IfParser):
- error_class = template.TemplateSyntaxError
-
- def __init__(self, parser, *args, **kwargs):
- self.template_parser = parser
- return super(TemplateIfParser, self).__init__(*args, **kwargs)
-
- def create_var(self, value):
- return self.template_parser.compile_filter(value)
-
-
-class SmartIfNode(template.Node):
- def __init__(self, var, nodelist_true, nodelist_false=None):
- self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
- self.var = var
-
- def render(self, context):
- if self.var.resolve(context):
- return self.nodelist_true.render(context)
- if self.nodelist_false:
- return self.nodelist_false.render(context)
- return ''
-
- def __repr__(self):
- return "<Smart If node>"
-
- def __iter__(self):
- for node in self.nodelist_true:
- yield node
- if self.nodelist_false:
- for node in self.nodelist_false:
- yield node
-
- def get_nodes_by_type(self, nodetype):
- nodes = []
- if isinstance(self, nodetype):
- nodes.append(self)
- nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
- if self.nodelist_false:
- nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
- return nodes
-
-
-@register.tag('if')
-def smart_if(parser, token):
- """
- A smarter {% if %} tag for django templates.
-
- While retaining current Django functionality, it also handles equality,
- greater than and less than operators. Some common case examples::
-
- {% if articles|length >= 5 %}...{% endif %}
- {% if "ifnotequal tag" != "beautiful" %}...{% endif %}
-
- Arguments and operators _must_ have a space between them, so
- ``{% if 1>2 %}`` is not a valid smart if tag.
-
- All supported operators are: ``or``, ``and``, ``in``, ``=`` (or ``==``),
- ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
- """
- bits = token.split_contents()[1:]
- var = TemplateIfParser(parser, bits).parse()
- nodelist_true = parser.parse(('else', 'endif'))
- token = parser.next_token()
- if token.contents == 'else':
- nodelist_false = parser.parse(('endif',))
- parser.delete_first_token()
- else:
- nodelist_false = None
- return SmartIfNode(var, nodelist_true, nodelist_false)
-
-
-if __name__ == '__main__':
- unittest.main()
+""" +A smarter {% if %} tag for django templates. + +While retaining current Django functionality, it also handles equality, +greater than and less than operators. Some common case examples:: + + {% if articles|length >= 5 %}...{% endif %} + {% if "ifnotequal tag" != "beautiful" %}...{% endif %} +""" +import unittest +from django import template + + +register = template.Library() + + +#============================================================================== +# Calculation objects +#============================================================================== + +class BaseCalc(object): + def __init__(self, var1, var2=None, negate=False): + self.var1 = var1 + self.var2 = var2 + self.negate = negate + + def resolve(self, context): + try: + var1, var2 = self.resolve_vars(context) + outcome = self.calculate(var1, var2) + except: + outcome = False + if self.negate: + return not outcome + return outcome + + def resolve_vars(self, context): + var2 = self.var2 and self.var2.resolve(context) + return self.var1.resolve(context), var2 + + def calculate(self, var1, var2): + raise NotImplementedError() + + +class Or(BaseCalc): + def calculate(self, var1, var2): + return var1 or var2 + + +class And(BaseCalc): + def calculate(self, var1, var2): + return var1 and var2 + + +class Equals(BaseCalc): + def calculate(self, var1, var2): + return var1 == var2 + + +class Greater(BaseCalc): + def calculate(self, var1, var2): + return var1 > var2 + + +class GreaterOrEqual(BaseCalc): + def calculate(self, var1, var2): + return var1 >= var2 + + +class In(BaseCalc): + def calculate(self, var1, var2): + return var1 in var2 + + +#============================================================================== +# Tests +#============================================================================== + +class TestVar(object): + """ + A basic self-resolvable object similar to a Django template variable. Used + to assist with tests. + """ + def __init__(self, value): + self.value = value + + def resolve(self, context): + return self.value + + +class SmartIfTests(unittest.TestCase): + def setUp(self): + self.true = TestVar(True) + self.false = TestVar(False) + self.high = TestVar(9000) + self.low = TestVar(1) + + def assertCalc(self, calc, context=None): + """ + Test a calculation is True, also checking the inverse "negate" case. + """ + context = context or {} + self.assert_(calc.resolve(context)) + calc.negate = not calc.negate + self.assertFalse(calc.resolve(context)) + + def assertCalcFalse(self, calc, context=None): + """ + Test a calculation is False, also checking the inverse "negate" case. + """ + context = context or {} + self.assertFalse(calc.resolve(context)) + calc.negate = not calc.negate + self.assert_(calc.resolve(context)) + + def test_or(self): + self.assertCalc(Or(self.true)) + self.assertCalcFalse(Or(self.false)) + self.assertCalc(Or(self.true, self.true)) + self.assertCalc(Or(self.true, self.false)) + self.assertCalc(Or(self.false, self.true)) + self.assertCalcFalse(Or(self.false, self.false)) + + def test_and(self): + self.assertCalc(And(self.true, self.true)) + self.assertCalcFalse(And(self.true, self.false)) + self.assertCalcFalse(And(self.false, self.true)) + self.assertCalcFalse(And(self.false, self.false)) + + def test_equals(self): + self.assertCalc(Equals(self.low, self.low)) + self.assertCalcFalse(Equals(self.low, self.high)) + + def test_greater(self): + self.assertCalc(Greater(self.high, self.low)) + self.assertCalcFalse(Greater(self.low, self.low)) + self.assertCalcFalse(Greater(self.low, self.high)) + + def test_greater_or_equal(self): + self.assertCalc(GreaterOrEqual(self.high, self.low)) + self.assertCalc(GreaterOrEqual(self.low, self.low)) + self.assertCalcFalse(GreaterOrEqual(self.low, self.high)) + + def test_in(self): + list_ = TestVar([1,2,3]) + invalid_list = TestVar(None) + self.assertCalc(In(self.low, list_)) + self.assertCalcFalse(In(self.low, invalid_list)) + + def test_parse_bits(self): + var = IfParser([True]).parse() + self.assert_(var.resolve({})) + var = IfParser([False]).parse() + self.assertFalse(var.resolve({})) + + var = IfParser([False, 'or', True]).parse() + self.assert_(var.resolve({})) + + var = IfParser([False, 'and', True]).parse() + self.assertFalse(var.resolve({})) + + var = IfParser(['not', False, 'and', 'not', False]).parse() + self.assert_(var.resolve({})) + + var = IfParser(['not', 'not', True]).parse() + self.assert_(var.resolve({})) + + var = IfParser([1, '=', 1]).parse() + self.assert_(var.resolve({})) + + var = IfParser([1, 'not', '=', 1]).parse() + self.assertFalse(var.resolve({})) + + var = IfParser([1, 'not', 'not', '=', 1]).parse() + self.assert_(var.resolve({})) + + var = IfParser([1, '!=', 1]).parse() + self.assertFalse(var.resolve({})) + + var = IfParser([3, '>', 2]).parse() + self.assert_(var.resolve({})) + + var = IfParser([1, '<', 2]).parse() + self.assert_(var.resolve({})) + + var = IfParser([2, 'not', 'in', [2, 3]]).parse() + self.assertFalse(var.resolve({})) + + var = IfParser([1, 'or', 1, '=', 2]).parse() + self.assert_(var.resolve({})) + + def test_boolean(self): + var = IfParser([True, 'and', True, 'and', True]).parse() + self.assert_(var.resolve({})) + var = IfParser([False, 'or', False, 'or', True]).parse() + self.assert_(var.resolve({})) + var = IfParser([True, 'and', False, 'or', True]).parse() + self.assert_(var.resolve({})) + var = IfParser([False, 'or', True, 'and', True]).parse() + self.assert_(var.resolve({})) + + var = IfParser([True, 'and', True, 'and', False]).parse() + self.assertFalse(var.resolve({})) + var = IfParser([False, 'or', False, 'or', False]).parse() + self.assertFalse(var.resolve({})) + var = IfParser([False, 'or', True, 'and', False]).parse() + self.assertFalse(var.resolve({})) + var = IfParser([False, 'and', True, 'or', False]).parse() + self.assertFalse(var.resolve({})) + + def test_invalid(self): + self.assertRaises(ValueError, IfParser(['not']).parse) + self.assertRaises(ValueError, IfParser(['==']).parse) + self.assertRaises(ValueError, IfParser([1, 'in']).parse) + self.assertRaises(ValueError, IfParser([1, '>', 'in']).parse) + self.assertRaises(ValueError, IfParser([1, '==', 'not', 'not']).parse) + self.assertRaises(ValueError, IfParser([1, 2]).parse) + + +OPERATORS = { + '=': (Equals, True), + '==': (Equals, True), + '!=': (Equals, False), + '>': (Greater, True), + '>=': (GreaterOrEqual, True), + '<=': (Greater, False), + '<': (GreaterOrEqual, False), + 'or': (Or, True), + 'and': (And, True), + 'in': (In, True), +} +BOOL_OPERATORS = ('or', 'and') + + +class IfParser(object): + error_class = ValueError + + def __init__(self, tokens): + self.tokens = tokens + + def _get_tokens(self): + return self._tokens + + def _set_tokens(self, tokens): + self._tokens = tokens + self.len = len(tokens) + self.pos = 0 + + tokens = property(_get_tokens, _set_tokens) + + def parse(self): + if self.at_end(): + raise self.error_class('No variables provided.') + var1 = self.get_bool_var() + while not self.at_end(): + op, negate = self.get_operator() + var2 = self.get_bool_var() + var1 = op(var1, var2, negate=negate) + return var1 + + def get_token(self, eof_message=None, lookahead=False): + negate = True + token = None + pos = self.pos + while token is None or token == 'not': + if pos >= self.len: + if eof_message is None: + raise self.error_class() + raise self.error_class(eof_message) + token = self.tokens[pos] + negate = not negate + pos += 1 + if not lookahead: + self.pos = pos + return token, negate + + def at_end(self): + return self.pos >= self.len + + def create_var(self, value): + return TestVar(value) + + def get_bool_var(self): + """ + Returns either a variable by itself or a non-boolean operation (such as + ``x == 0`` or ``x < 0``). + + This is needed to keep correct precedence for boolean operations (i.e. + ``x or x == 0`` should be ``x or (x == 0)``, not ``(x or x) == 0``). + """ + var = self.get_var() + if not self.at_end(): + op_token = self.get_token(lookahead=True)[0] + if isinstance(op_token, basestring) and (op_token not in + BOOL_OPERATORS): + op, negate = self.get_operator() + return op(var, self.get_var(), negate=negate) + return var + + def get_var(self): + token, negate = self.get_token('Reached end of statement, still ' + 'expecting a variable.') + if isinstance(token, basestring) and token in OPERATORS: + raise self.error_class('Expected variable, got operator (%s).' % + token) + var = self.create_var(token) + if negate: + return Or(var, negate=True) + return var + + def get_operator(self): + token, negate = self.get_token('Reached end of statement, still ' + 'expecting an operator.') + if not isinstance(token, basestring) or token not in OPERATORS: + raise self.error_class('%s is not a valid operator.' % token) + if self.at_end(): + raise self.error_class('No variable provided after "%s".' % token) + op, true = OPERATORS[token] + if not true: + negate = not negate + return op, negate + + +#============================================================================== +# Actual templatetag code. +#============================================================================== + +class TemplateIfParser(IfParser): + error_class = template.TemplateSyntaxError + + def __init__(self, parser, *args, **kwargs): + self.template_parser = parser + return super(TemplateIfParser, self).__init__(*args, **kwargs) + + def create_var(self, value): + return self.template_parser.compile_filter(value) + + +class SmartIfNode(template.Node): + def __init__(self, var, nodelist_true, nodelist_false=None): + self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false + self.var = var + + def render(self, context): + if self.var.resolve(context): + return self.nodelist_true.render(context) + if self.nodelist_false: + return self.nodelist_false.render(context) + return '' + + def __repr__(self): + return "<Smart If node>" + + def __iter__(self): + for node in self.nodelist_true: + yield node + if self.nodelist_false: + for node in self.nodelist_false: + yield node + + def get_nodes_by_type(self, nodetype): + nodes = [] + if isinstance(self, nodetype): + nodes.append(self) + nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) + if self.nodelist_false: + nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) + return nodes + + +@register.tag('if') +def smart_if(parser, token): + """ + A smarter {% if %} tag for django templates. + + While retaining current Django functionality, it also handles equality, + greater than and less than operators. Some common case examples:: + + {% if articles|length >= 5 %}...{% endif %} + {% if "ifnotequal tag" != "beautiful" %}...{% endif %} + + Arguments and operators _must_ have a space between them, so + ``{% if 1>2 %}`` is not a valid smart if tag. + + All supported operators are: ``or``, ``and``, ``in``, ``=`` (or ``==``), + ``!=``, ``>``, ``>=``, ``<`` and ``<=``. + """ + bits = token.split_contents()[1:] + var = TemplateIfParser(parser, bits).parse() + nodelist_true = parser.parse(('else', 'endif')) + token = parser.next_token() + if token.contents == 'else': + nodelist_false = parser.parse(('endif',)) + parser.delete_first_token() + else: + nodelist_false = None + return SmartIfNode(var, nodelist_true, nodelist_false) + + +if __name__ == '__main__': + unittest.main() diff --git a/forum/urls.py b/forum/urls.py index 62e70161..42746d44 100644 --- a/forum/urls.py +++ b/forum/urls.py @@ -54,7 +54,7 @@ urlpatterns = patterns('', app.delete_comment, kwargs={'commented_object_type':'answer'}, \ name='delete_answer_comment'), \ #place general question item in the end of other operations - url(r'^%s(?P<id>\d+)//*' % _('question/'), app.question, name='question'), + 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, name='tag_questions'), @@ -86,6 +86,7 @@ urlpatterns = patterns('', url(r'^%s(?P<short_name>[^/]+)/$' % _('books/'), app.book, name='book'), url(r'^%s$' % _('search/'), app.search, name='search'), url(r'^%s$' % _('feedback/'), app.feedback, name='feedback'), + (r'^%sfb/' % _('account/'), include('fbconnect.urls')), (r'^%s' % _('account/'), include('django_authopenid.urls')), (r'^i18n/', include('django.conf.urls.i18n')), ) diff --git a/forum/views.py b/forum/views.py index 296745f9..ade6aaa7 100644 --- a/forum/views.py +++ b/forum/views.py @@ -33,7 +33,7 @@ from forum.auth import * from forum.const import * from forum.user import * from forum import auth -from django_authopenid.util import get_next_url +from utils.forms import get_next_url # used in index page INDEX_PAGE_SIZE = 20 @@ -202,7 +202,7 @@ def questions(request, tagname=None, unanswered=False): '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 forum_markedtag.reason = \'good\' ' + 'AND question_tags.question_id = question.id' ), ]), @@ -220,7 +220,7 @@ def questions(request, tagname=None, unanswered=False): '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 forum_markedtag.reason = \'bad\' ' + 'AND question_tags.question_id = question.id' ) ]), @@ -436,6 +436,21 @@ def question(request, id): logging.debug('view_id=' + str(view_id)) question = get_object_or_404(Question, id=id) + try: + pattern = r'/%s%s%d/([\w-]+)' % (settings.FORUM_SCRIPT_ALIAS,_('question/'), question.id) + path_re = re.compile(pattern) + logging.debug(pattern) + logging.debug(request.path) + m = path_re.match(request.path) + if m: + slug = m.group(1) + logging.debug('have slug %s' % slug) + assert(slug == slugify(question.title)) + else: + logging.debug('no match!') + except: + return HttpResponseRedirect(question.get_absolute_url()) + if question.deleted and not can_view_deleted_post(request.user, question): raise Http404 answer_form = AnswerForm(question,request.user) @@ -1236,7 +1251,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.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']) @@ -1273,7 +1288,7 @@ def user_stats(request, user_id, user_view): }, select_params=[user_id], tables=['question', 'auth_user'], - where=['question.deleted = 0 AND question.author_id=%s AND question.last_activity_by_id = auth_user.id'], + where=['question.deleted=False AND question.author_id=%s AND question.last_activity_by_id = auth_user.id'], params=[user_id], order_by=['-vote_count', '-last_activity_at'] ).values('vote_count', @@ -1309,7 +1324,7 @@ def user_stats(request, user_id, user_view): 'comment_count' : 'answer.comment_count' }, tables=['question', 'answer'], - where=['answer.deleted=0 AND question.deleted=0 AND answer.author_id=%s AND answer.question_id=question.id'], + where=['answer.deleted=False AND question.deleted=False AND answer.author_id=%s AND answer.question_id=question.id'], params=[user_id], order_by=['-vote_count', '-answer_id'], select_params=[user_id] @@ -1431,7 +1446,7 @@ def user_recent(request, user_id, user_view): }, tables=['activity', 'question'], where=['activity.content_type_id = %s AND activity.object_id = ' + - 'question.id AND question.deleted=0 AND activity.user_id = %s AND activity.activity_type = %s'], + 'question.id AND question.deleted=False AND activity.user_id = %s AND activity.activity_type = %s'], params=[question_type_id, user_id, TYPE_ACTIVITY_ASK_QUESTION], order_by=['-activity.active_at'] ).values( @@ -1456,8 +1471,8 @@ def user_recent(request, user_id, user_view): }, tables=['activity', 'answer', 'question'], where=['activity.content_type_id = %s AND activity.object_id = answer.id AND ' + - 'answer.question_id=question.id AND answer.deleted=0 AND activity.user_id=%s AND '+ - 'activity.activity_type=%s AND question.deleted=0'], + 'answer.question_id=question.id AND answer.deleted=False AND activity.user_id=%s AND '+ + 'activity.activity_type=%s AND question.deleted=False'], params=[answer_type_id, user_id, TYPE_ACTIVITY_ANSWER], order_by=['-activity.active_at'] ).values( @@ -1485,7 +1500,7 @@ def user_recent(request, user_id, user_view): where=['activity.content_type_id = %s AND activity.object_id = comment.id AND '+ 'activity.user_id = comment.user_id AND comment.object_id=question.id AND '+ 'comment.content_type_id=%s AND activity.user_id = %s AND activity.activity_type=%s AND ' + - 'question.deleted=0'], + 'question.deleted=False'], params=[comment_type_id, question_type_id, user_id, TYPE_ACTIVITY_COMMENT_QUESTION], order_by=['-comment.added_at'] ).values( @@ -1515,7 +1530,7 @@ def user_recent(request, user_id, user_view): 'activity.user_id = comment.user_id AND comment.object_id=answer.id AND '+ 'comment.content_type_id=%s AND question.id = answer.question_id AND '+ 'activity.user_id = %s AND activity.activity_type=%s AND '+ - 'answer.deleted=0 AND question.deleted=0'], + 'answer.deleted=False AND question.deleted=False'], params=[comment_type_id, answer_type_id, user_id, TYPE_ACTIVITY_COMMENT_ANSWER], order_by=['-comment.added_at'] ).values( @@ -1542,7 +1557,7 @@ def user_recent(request, user_id, user_view): }, tables=['activity', 'question_revision', 'question'], where=['activity.content_type_id = %s AND activity.object_id = question_revision.id AND '+ - 'question_revision.id=question.id AND question.deleted=0 AND '+ + 'question_revision.id=question.id AND question.deleted=False AND '+ 'activity.user_id = question_revision.author_id AND activity.user_id = %s AND '+ 'activity.activity_type=%s'], params=[question_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_QUESTION], @@ -1575,7 +1590,7 @@ def user_recent(request, user_id, user_view): where=['activity.content_type_id = %s AND activity.object_id = answer_revision.id AND '+ 'activity.user_id = answer_revision.author_id AND activity.user_id = %s AND '+ 'answer_revision.answer_id=answer.id AND answer.question_id = question.id AND '+ - 'question.deleted=0 AND answer.deleted=0 AND '+ + 'question.deleted=False AND answer.deleted=False AND '+ 'activity.activity_type=%s'], params=[answer_revision_type_id, user_id, TYPE_ACTIVITY_UPDATE_ANSWER], order_by=['-activity.active_at'] @@ -1604,7 +1619,7 @@ def user_recent(request, user_id, user_view): tables=['activity', 'answer', 'question'], where=['activity.content_type_id = %s AND activity.object_id = answer.id AND '+ 'activity.user_id = question.author_id AND activity.user_id = %s AND '+ - 'answer.deleted=0 AND question.deleted=0 AND '+ + 'answer.deleted=False AND question.deleted=False AND '+ 'answer.question_id=question.id AND activity.activity_type=%s'], params=[answer_type_id, user_id, TYPE_ACTIVITY_MARK_ANSWER], order_by=['-activity.active_at'] @@ -1680,7 +1695,7 @@ def user_responses(request, user_id, user_view): }, select_params=[user_id], tables=['answer', 'question', 'auth_user'], - where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+ + where=['answer.question_id = question.id AND answer.deleted=False AND question.deleted=False 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'] @@ -1711,7 +1726,7 @@ def user_responses(request, user_id, user_view): '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 '+ + where=['question.deleted=False 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'] @@ -1741,7 +1756,7 @@ def user_responses(request, user_id, user_view): 'user_id' : 'auth_user.id' }, tables=['answer', 'auth_user', 'comment', 'question'], - where=['answer.deleted = 0 AND answer.author_id = %s AND comment.object_id=answer.id AND '+ + where=['answer.deleted=False AND answer.author_id = %s AND comment.object_id=answer.id AND '+ 'comment.content_type_id=%s AND comment.user_id <> %s AND comment.user_id = auth_user.id '+ 'AND question.id = answer.question_id'], params=[user_id, answer_type_id, user_id], @@ -1774,8 +1789,8 @@ def user_responses(request, user_id, user_view): }, select_params=[user_id], tables=['answer', 'question', 'auth_user'], - where=['answer.question_id = question.id AND answer.deleted=0 AND question.deleted = 0 AND '+ - 'answer.author_id = %s AND answer.accepted=1 AND question.author_id=auth_user.id'], + where=['answer.question_id = question.id AND answer.deleted=False AND question.deleted=False AND '+ + 'answer.author_id = %s AND answer.accepted=True AND question.author_id=auth_user.id'], params=[user_id], order_by=['-answer.id'] ).values( @@ -1922,7 +1937,7 @@ def user_favorites(request, user_id, user_view): }, select_params=[user_id], tables=['question', 'auth_user', 'favorite_question'], - where=['question.deleted = 0 AND question.last_activity_by_id = auth_user.id '+ + where=['question.deleted=True AND question.last_activity_by_id = auth_user.id '+ 'AND favorite_question.question_id = question.id AND favorite_question.user_id = %s'], params=[user_id], order_by=['-vote_count', '-question.id'] @@ -2093,7 +2108,7 @@ def badge(request, id): tables=['award', 'auth_user'], where=['badge_id=%s AND user_id=auth_user.id'], params=[id] - ).values('id').distinct() + ).distinct('id') return render_to_response('badge.html', { 'awards' : awards, @@ -2337,8 +2352,18 @@ def search(request): except KeyError: view_id = "latest" orderby = "-added_at" - - if settings.USE_SPHINX_SEARCH == True: + + if settings.USE_PG_FTS: + objects = Question.objects.filter(deleted=False).extra( + select={ + 'ranking': "ts_rank_cd(tsv, plainto_tsquery(%s), 32)", + }, + where=["tsv @@ plainto_tsquery(%s)"], + params=[keywords], + select_params=[keywords] + ).order_by('-ranking') + + elif 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) @@ -2359,6 +2384,9 @@ def search(request): if tag not in related_tags: related_tags.append(tag) + #if is_search is true in the context, prepend this string to soting tabs urls + search_uri = "?q=%s&page=%d&t=question" % ("+".join(keywords.split()), page) + return render_to_response(template_file, { "questions" : questions, "tab_id" : view_id, @@ -2368,6 +2396,8 @@ def search(request): "searchtitle" : keywords, "keywords" : keywords, "is_unanswered" : False, + "is_search": True, + "search_uri": search_uri, "context" : { 'is_paginated' : True, 'pages': objects_list.num_pages, |