diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2010-06-03 01:44:36 -0700 |
---|---|---|
committer | fadeev <fadeev@bacchus.bio.uci.edu> | 2010-06-03 01:45:30 -0700 |
commit | 02247d157b3da4d0711b8858bf758aefdf64c04d (patch) | |
tree | a3231ac1392bc815bdd75a1e2d0ed21fa94fbe4d | |
parent | 5dfebd5164518572b9f1c9822990ee476854304d (diff) | |
download | askbot-02247d157b3da4d0711b8858bf758aefdf64c04d.tar.gz askbot-02247d157b3da4d0711b8858bf758aefdf64c04d.tar.bz2 askbot-02247d157b3da4d0711b8858bf758aefdf64c04d.zip |
general debugging, still may be broken
45 files changed, 966 insertions, 697 deletions
diff --git a/forum/__init__.py b/forum/__init__.py index 85cd5d26..d4d26875 100644 --- a/forum/__init__.py +++ b/forum/__init__.py @@ -1 +1,14 @@ -__all__ = ['admin','auth','const','feed','forms','managers','models','sitemap','urls','views'] +"""aksbot forum module +""" +__all__ = [ + 'admin', + 'auth', + 'const', + 'feed', + 'forms', + 'managers', + 'models', + 'sitemap', + 'urls', + 'views' +] diff --git a/forum/admin.py b/forum/admin.py index 41b68b9a..c5898574 100644 --- a/forum/admin.py +++ b/forum/admin.py @@ -1,6 +1,9 @@ +""" +linking of forum modules to admin interface +""" # -*- coding: utf-8 -*- from django.contrib import admin -from forum.models import * +from forum import models class AnonymousQuestionAdmin(admin.ModelAdmin): """AnonymousQuestion admin class""" @@ -53,19 +56,19 @@ class ActivityAdmin(admin.ModelAdmin): #class BookAuthorRssAdmin(admin.ModelAdmin): # """ admin class""" -admin.site.register(Question, QuestionAdmin) -admin.site.register(Tag, TagAdmin) -admin.site.register(Answer, Answerdmin) -admin.site.register(Comment, CommentAdmin) -admin.site.register(Vote, VoteAdmin) -admin.site.register(FlaggedItem, FlaggedItemAdmin) -admin.site.register(FavoriteQuestion, FavoriteQuestionAdmin) -admin.site.register(QuestionRevision, QuestionRevisionAdmin) -admin.site.register(AnswerRevision, AnswerRevisionAdmin) -admin.site.register(Badge, BadgeAdmin) -admin.site.register(Award, AwardAdmin) -admin.site.register(Repute, ReputeAdmin) -admin.site.register(Activity, ActivityAdmin) +admin.site.register(models.Question, QuestionAdmin) +admin.site.register(models.Tag, TagAdmin) +admin.site.register(models.Answer, Answerdmin) +admin.site.register(models.Comment, CommentAdmin) +admin.site.register(models.Vote, VoteAdmin) +admin.site.register(models.FlaggedItem, FlaggedItemAdmin) +admin.site.register(models.FavoriteQuestion, FavoriteQuestionAdmin) +admin.site.register(models.QuestionRevision, QuestionRevisionAdmin) +admin.site.register(models.AnswerRevision, AnswerRevisionAdmin) +admin.site.register(models.Badge, BadgeAdmin) +admin.site.register(models.Award, AwardAdmin) +admin.site.register(models.Repute, ReputeAdmin) +admin.site.register(models.Activity, ActivityAdmin) #admin.site.register(Book, BookAdmin) #admin.site.register(BookAuthorInfo, BookAuthorInfoAdmin) #admin.site.register(BookAuthorRss, BookAuthorRssAdmin) diff --git a/forum/auth.py b/forum/auth.py index 7664a02e..e88c280e 100644 --- a/forum/auth.py +++ b/forum/auth.py @@ -7,11 +7,10 @@ and superuser status. import datetime from django.utils.translation import ugettext as _ from django.db import transaction -from models import Repute -from models import Question -from models import Answer -from models import signals -from const import TYPE_REPUTATION +from forum.models import Repute +from forum.models import Question +from forum.models import Answer +from forum.models import signals import logging from forum.conf import settings as forum_settings @@ -31,7 +30,7 @@ def can_flag_offensive(user): user.reputation >= forum_settings.MIN_REP_TO_FLAG_OFFENSIVE or user.is_superuser) -def can_add_comments(user,subject): +def can_add_comments(user, subject): """Determines if a User can add comments to Questions and Answers.""" if user.is_authenticated(): if user.id == subject.author.id: @@ -40,31 +39,43 @@ def can_add_comments(user,subject): return True if user.is_superuser: return True - if isinstance(subject,Answer) and subject.question.author.id == user.id: - return True + if isinstance(subject, Answer): + if subject.question.author.id == user.id: + return True return False def can_vote_down(user): """Determines if a User can vote Questions and Answers down.""" - return user.is_authenticated() and ( - user.reputation >= forum_settings.MIN_REP_TO_VOTE_DOWN or - user.is_superuser) + if user.is_authenticated(): + if user.reputation >= forum_settings.MIN_REP_TO_VOTE_DOWN: + return True + if user.is_superuser: + return True + return False def can_retag_questions(user): """Determines if a User can retag Questions.""" - return user.is_authenticated() and ( - forum_settings.MIN_REP_TO_RETAG_OTHERS_QUESTIONS - <= user.reputation - < forum_settings.MIN_REP_TO_EDIT_OTHERS_POSTS or - user.is_superuser) + if user.is_authenticated(): + if user.reputation >= forum_settings.MIN_REP_TO_RETAG_OTHERS_QUESTIONS: + if user.reputation < forum_settings.MIN_REP_TO_EDIT_OTHERS_POSTS: + return True + if user.is_superuser: + return True + return False def can_edit_post(user, post): """Determines if a User can edit the given Question or Answer.""" - return user.is_authenticated() and ( - user.id == post.author_id or - (post.wiki and user.reputation >= forum_settings.MIN_REP_TO_EDIT_WIKI) or - user.reputation >= forum_settings.MIN_REP_TO_EDIT_OTHERS_POSTS or - user.is_superuser) + if user.is_authenticated(): + if user.id == post.author_id: + return True + if post.wiki: + if user.reputation >= forum_settings.MIN_REP_TO_EDIT_WIKI: + return True + if user.reputation >= forum_settings.MIN_REP_TO_EDIT_OTHERS_POSTS: + return True + if user.is_superuser: + return True + return False def can_delete_comment(user, comment): """Determines if a User can delete the given Comment.""" @@ -98,23 +109,29 @@ def can_follow_url(user): return user.reputation >= forum_settings.MIN_REP_TO_DISABLE_URL_NOFOLLOW def can_accept_answer(user, question, answer): - return (user.is_authenticated() and - question.author != answer.author and - question.author == user) or user.is_superuser + if user.is_superuser: + return True + if user.is_authenticated(): + if question.author != answer.author and question.author == user: + return True + return False # now only support to reopen own question except superuser def can_reopen_question(user, question): - return (user.is_authenticated() and - user.id == question.author_id and - user.reputation >= forum_settings.MIN_REP_TO_REOPEN_OWN_QUESTIONS) or user.is_superuser + if user.is_superuser: + return True + if user.is_authenticated() and user.id == question.author_id: + if user.reputation >= forum_settings.MIN_REP_TO_REOPEN_OWN_QUESTIONS: + return True + return False def can_delete_post(user, post): if user.is_superuser: return True elif user.is_authenticated() and user == post.author: - if isinstance(post,Answer): + if isinstance(post, Answer): return True - elif isinstance(post,Question): + elif isinstance(post, Question): answers = post.answers.all() for answer in answers: if user != answer.author and answer.deleted == False: @@ -142,9 +159,12 @@ def can_view_user_edit(request_user, target_user): return (request_user.is_authenticated() and request_user == target_user) def can_upload_files(request_user): - return (request_user.is_authenticated() and - request_user.reputation >= forum_settings.MIN_REP_TO_UPLOAD_FILES) or \ - request_user.is_superuser + if request_user.is_superuser: + return True + if request_user.is_authenticated(): + if request_user.reputation >= forum_settings.MIN_REP_TO_UPLOAD_FILES: + return True + return False ########################################### ## actions and reputation changes event @@ -165,48 +185,64 @@ def onFlaggedItem(item, post, user, timestamp=None): post.offensive_flag_count = post.offensive_flag_count + 1 post.save() - post.author.reputation = calculate_reputation(post.author.reputation, - forum_settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE) + post.author.reputation = calculate_reputation( + post.author.reputation, + forum_settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE + ) post.author.save() question = post if isinstance(post, Answer): question = post.question - reputation = Repute(user=post.author, - negative=forum_settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE, - question=question, reputed_at=timestamp, - reputation_type=-4, - reputation=post.author.reputation) + reputation = Repute( + user=post.author, + negative=forum_settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE, + question=question, reputed_at=timestamp, + reputation_type=-4, + reputation=post.author.reputation + ) reputation.save() #todo: These should be updated to work on same revisions. if post.offensive_flag_count == forum_settings.MIN_FLAGS_TO_HIDE_POST: - post.author.reputation = calculate_reputation(post.author.reputation, - forum_settings.REP_LOSS_FOR_RECEIVING_THREE_FLAGS_PER_REVISION) + post.author.reputation = \ + calculate_reputation( + post.author.reputation, + forum_settings.REP_LOSS_FOR_RECEIVING_THREE_FLAGS_PER_REVISION + ) post.author.save() - reputation = Repute(user=post.author, - negative=forum_settings.REP_LOSS_FOR_RECEIVING_THREE_FLAGS_PER_REVISION, - question=question, - reputed_at=timestamp, - reputation_type=-6, - reputation=post.author.reputation) + reputation = Repute( + user=post.author, + negative=\ + forum_settings.REP_LOSS_FOR_RECEIVING_THREE_FLAGS_PER_REVISION, + question=question, + reputed_at=timestamp, + reputation_type=-6, + reputation=post.author.reputation + ) reputation.save() elif post.offensive_flag_count == forum_settings.MIN_FLAGS_TO_DELETE_POST: - post.author.reputation = calculate_reputation(post.author.reputation, - forum_settings.REP_LOSS_FOR_RECEIVING_FIVE_FLAGS_PER_REVISION) + post.author.reputation = \ + calculate_reputation( + post.author.reputation, + forum_settings.REP_LOSS_FOR_RECEIVING_FIVE_FLAGS_PER_REVISION + ) post.author.save() - reputation = Repute(user=post.author, - negative=forum_settings.REP_LOSS_FOR_RECEIVING_FIVE_FLAGS_PER_REVISION, - question=question, - reputed_at=timestamp, - reputation_type=-7, - reputation=post.author.reputation) + reputation = Repute( + user=post.author, + negative=\ + forum_settings.REP_LOSS_FOR_RECEIVING_FIVE_FLAGS_PER_REVISION, + question=question, + reputed_at=timestamp, + reputation_type=-7, + reputation=post.author.reputation + ) reputation.save() post.deleted = True @@ -231,9 +267,9 @@ def onAnswerAccept(answer, user, timestamp=None): answer.question.save() answer.author.reputation = calculate_reputation( - answer.author.reputation, - forum_settings.REP_GAIN_FOR_RECEIVING_ANSWER_ACCEPTANCE - ) + answer.author.reputation, + forum_settings.REP_GAIN_FOR_RECEIVING_ANSWER_ACCEPTANCE + ) answer.author.save() reputation = Repute(user=answer.author, positive=forum_settings.REP_GAIN_FOR_RECEIVING_ANSWER_ACCEPTANCE, @@ -264,15 +300,20 @@ def onAnswerAcceptCanceled(answer, user, timestamp=None): answer.save() answer.question.save() - answer.author.reputation = calculate_reputation(answer.author.reputation, - forum_settings.REP_LOSS_FOR_RECEIVING_CANCELATION_OF_ANSWER_ACCEPTANCE) + answer.author.reputation = calculate_reputation( + answer.author.reputation, + forum_settings.REP_LOSS_FOR_RECEIVING_CANCELATION_OF_ANSWER_ACCEPTANCE + ) answer.author.save() - reputation = Repute(user=answer.author, - negative=forum_settings.REP_LOSS_FOR_RECEIVING_CANCELATION_OF_ANSWER_ACCEPTANCE, - question=answer.question, - reputed_at=timestamp, - reputation_type=-2, - reputation=answer.author.reputation) + reputation = Repute( + user=answer.author, + negative=\ + forum_settings.REP_LOSS_FOR_RECEIVING_CANCELATION_OF_ANSWER_ACCEPTANCE, + question=answer.question, + reputed_at=timestamp, + reputation_type=-2, + reputation=answer.author.reputation + ) reputation.save() user.reputation = calculate_reputation(user.reputation, @@ -330,20 +371,25 @@ def onUpVotedCanceled(vote, post, user, timestamp=None): if not post.wiki: author = post.author - author.reputation = calculate_reputation(author.reputation, - forum_settings.REP_LOSS_FOR_RECEIVING_UPVOTE_CANCELATION) + author.reputation = \ + calculate_reputation( + author.reputation, + forum_settings.REP_LOSS_FOR_RECEIVING_UPVOTE_CANCELATION + ) author.save() question = post if isinstance(post, Answer): question = post.question - reputation = Repute(user=author, - negative=forum_settings.REP_LOSS_FOR_RECEIVING_UPVOTE_CANCELATION, - question=question, - reputed_at=timestamp, - reputation_type=-8, - reputation=author.reputation) + reputation = Repute( + user=author, + negative=forum_settings.REP_LOSS_FOR_RECEIVING_UPVOTE_CANCELATION, + question=question, + reputed_at=timestamp, + reputation_type=-8, + reputation=author.reputation + ) reputation.save() @transaction.commit_on_success @@ -358,8 +404,10 @@ def onDownVoted(vote, post, user, timestamp=None): if not post.wiki: author = post.author - author.reputation = calculate_reputation(author.reputation, - forum_settings.REP_LOSS_FOR_DOWNVOTING) + author.reputation = calculate_reputation( + author.reputation, + forum_settings.REP_LOSS_FOR_DOWNVOTING + ) author.save() question = post @@ -374,8 +422,10 @@ def onDownVoted(vote, post, user, timestamp=None): reputation=author.reputation) reputation.save() - user.reputation = calculate_reputation(user.reputation, - forum_settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE) + user.reputation = calculate_reputation( + user.reputation, + forum_settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE + ) user.save() reputation = Repute(user=user, @@ -400,8 +450,10 @@ def onDownVotedCanceled(vote, post, user, timestamp=None): if not post.wiki: author = post.author - author.reputation = calculate_reputation(author.reputation, - forum_settings.REP_GAIN_FOR_RECEIVING_DOWNVOTE_CANCELATION) + author.reputation = calculate_reputation( + author.reputation, + forum_settings.REP_GAIN_FOR_RECEIVING_DOWNVOTE_CANCELATION + ) author.save() question = post @@ -409,11 +461,13 @@ def onDownVotedCanceled(vote, post, user, timestamp=None): question = post.question reputation = Repute(user=author, - positive=forum_settings.REP_GAIN_FOR_RECEIVING_DOWNVOTE_CANCELATION, - question=question, - reputed_at=timestamp, - reputation_type=4, - reputation=author.reputation) + positive=\ + forum_settings.REP_GAIN_FOR_RECEIVING_DOWNVOTE_CANCELATION, + question=question, + reputed_at=timestamp, + reputation_type=4, + reputation=author.reputation + ) reputation.save() user.reputation = calculate_reputation(user.reputation, @@ -435,10 +489,13 @@ def onDeleteCanceled(post, user, timestamp=None): post.deleted_at = None post.save() logging.debug('now restoring something') - if isinstance(post,Answer): - logging.debug('updated answer count on undelete, have %d' % post.question.answer_count) + if isinstance(post, Answer): + logging.debug( + 'updated answer count on undelete, have %d' \ + % post.question.answer_count + ) Question.objects.update_answer_count(post.question) - elif isinstance(post,Question): + elif isinstance(post, Question): for tag in list(post.tags.all()): if tag.used_count == 1 and tag.deleted: tag.deleted = False @@ -467,12 +524,16 @@ def onDeleted(post, user, timestamp=None): answers = post.answers.all() if user == post.author: if len(answers) > 0: - msg = _('Your question and all of it\'s answers have been deleted') + msg = _( + 'Your question and all of it\'s answers have been deleted' + ) else: msg = _('Your question has been deleted') else: if len(answers) > 0: - msg = _('The question and all of it\'s answers have been deleted') + msg = _( + 'The question and all of it\'s answers have been deleted' + ) else: msg = _('The question has been deleted') user.message_set.create(message=msg) diff --git a/forum/conf/site_settings.py b/forum/conf/site_settings.py index 19445bda..05d0c1c8 100644 --- a/forum/conf/site_settings.py +++ b/forum/conf/site_settings.py @@ -6,7 +6,6 @@ from forum.conf.settings_wrapper import settings from livesettings import ConfigurationGroup, StringValue from django.utils.translation import ugettext as _ from django.utils.html import escape -from django.conf import settings as django_settings from forum import const QA_SITE_SETTINGS = ConfigurationGroup( diff --git a/forum/conf/skin_general_settings.py b/forum/conf/skin_general_settings.py index 166cd603..b1674d37 100644 --- a/forum/conf/skin_general_settings.py +++ b/forum/conf/skin_general_settings.py @@ -4,7 +4,6 @@ General skin settings from forum.conf.settings_wrapper import settings from livesettings import ConfigurationGroup, StringValue, IntegerValue from django.utils.translation import ugettext as _ -from forum.skins import get_skin_choices GENERAL_SKIN_SETTINGS = ConfigurationGroup( 'GENERAL_SKIN_SETTINGS', @@ -16,7 +15,7 @@ settings.register( GENERAL_SKIN_SETTINGS, 'ASKBOT_DEFAULT_SKIN', default='default', - choices=get_skin_choices(), + choices=[('default','default')],#todo: get_skin_choices(), description=_('Select skin'), ) ) diff --git a/forum/const/__init__.py b/forum/const/__init__.py index fcba80d1..e83c5a12 100644 --- a/forum/const/__init__.py +++ b/forum/const/__init__.py @@ -55,7 +55,7 @@ POST_SCOPE_LIST = ( ('favorite', _('favorite')), ) DEFAULT_POST_SCOPE = 'all' -PAGE_SIZE_CHOICES = (('10','10',),('30','30',),('50','50',),) +PAGE_SIZE_CHOICES = (('10', '10',), ('30', '30',), ('50', '50',),) #todo: remove this duplication QUESTIONS_PER_PAGE_USER_CHOICES = ( (10, u'10'), diff --git a/forum/const/message_keys.py b/forum/const/message_keys.py index c52b9353..f7f8e8e3 100644 --- a/forum/const/message_keys.py +++ b/forum/const/message_keys.py @@ -9,7 +9,7 @@ _ = lambda v:v #NOTE: all strings must be explicitly put into this dictionary, #because you don't want to import _ from here with import * -__all__ = ['GREETING_FOR_ANONYMOUS_USER',] +__all__ = ['GREETING_FOR_ANONYMOUS_USER', ] #this variable is shown in settings, because #the url within is configurable, the default is reverse('faq') diff --git a/forum/feed.py b/forum/feed.py index 94065120..779049c7 100644 --- a/forum/feed.py +++ b/forum/feed.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -#encoding:utf-8 +""" #------------------------------------------------------------------------------- # Name: Syndication feed class for subscription # Purpose: @@ -10,11 +9,16 @@ # Copyright: (c) CNPROG.COM 2009 # Licence: GPL V2 #------------------------------------------------------------------------------- +""" +#!/usr/bin/env python +#encoding:utf-8 from django.contrib.syndication.feeds import Feed, FeedDoesNotExist from django.utils.translation import ugettext as _ -from models import Question +from forum.models import Question from forum.conf import settings as forum_settings class RssLastestQuestionsFeed(Feed): + """rss feed class for the latest questions + """ title = forum_settings.APP_TITLE + _(' - ')+ _('latest questions') link = forum_settings.APP_URL description = forum_settings.APP_DESCRIPTION @@ -22,21 +26,37 @@ class RssLastestQuestionsFeed(Feed): copyright = forum_settings.APP_COPYRIGHT def item_link(self, item): + """get full url to the item + """ return self.link + item.get_absolute_url() def item_author_name(self, item): + """get name of author + """ return item.author.username def item_author_link(self, item): + """get url of the author's profile + """ return item.author.get_profile_url() def item_pubdate(self, item): + """get date of creation for the item + """ return item.added_at def items(self, item): - return Question.objects.filter(deleted=False).order_by('-last_activity_at')[:30] + """get questions for the feed + """ + return Question.objects.filter( + deleted=False + ).order_by( + '-last_activity_at' + )[:30] def main(): + """main function for use as a script + """ pass if __name__ == '__main__': diff --git a/forum/forms.py b/forum/forms.py index d5ca388b..38f8f2fd 100644 --- a/forum/forms.py +++ b/forum/forms.py @@ -1,16 +1,13 @@ import re -from datetime import date from django import forms -from models import * -from const import * #todo: clean out import * thing +from forum import models from forum import const from django.utils.translation import ugettext as _ from django.utils.translation import ungettext from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType -from forum.utils.forms import NextUrlField, UserNameField, SetPasswordForm +from forum.utils.forms import NextUrlField, UserNameField from recaptcha_django import ReCaptchaField -from django.conf import settings from forum.conf import settings as forum_settings import logging @@ -19,7 +16,9 @@ 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.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') @@ -98,7 +97,7 @@ class WikiField(forms.BooleanField): 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): + def clean(self, value): return value and forum_settings.WIKI_ON class EmailNotifyField(forms.BooleanField): @@ -139,7 +138,7 @@ class AdvancedSearchForm(forms.Form): reset_author = forms.BooleanField(required=False) reset_query = forms.BooleanField(required=False) start_over = forms.BooleanField(required=False) - tags = forms.CharField(max_length=256,required=False) + tags = forms.CharField(max_length=256, required=False) author = forms.IntegerField(required=False) page_size = forms.ChoiceField(choices=const.PAGE_SIZE_CHOICES, required=False) page = forms.IntegerField(required=False) @@ -147,7 +146,7 @@ class AdvancedSearchForm(forms.Form): def clean_tags(self): if 'tags' in self.cleaned_data: tags_input = self.cleaned_data['tags'].strip() - split_re = re.compile(TAG_SPLIT_REGEX) + split_re = re.compile(const.TAG_SPLIT_REGEX) tag_strings = split_re.split(tags_input) tagname_re = re.compile(const.TAG_REGEX, re.UNICODE) out = set() @@ -232,7 +231,7 @@ class AnswerForm(forms.Form): 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'; + self.fields['email_notify'].widget.attrs['id'] = 'question-subscribe-updates' if question.wiki and forum_settings.WIKI_ON: self.fields['wiki'].initial = True if user.is_authenticated(): @@ -243,7 +242,7 @@ class AnswerForm(forms.Form): class CloseForm(forms.Form): - reason = forms.ChoiceField(choices=CLOSE_REASONS) + reason = forms.ChoiceField(choices=const.CLOSE_REASONS) class RetagQuestionForm(forms.Form): tags = TagNamesField() @@ -327,7 +326,7 @@ class EditUserForm(forms.Form): if forum_settings.EMAIL_UNIQUE == True: if 'email' in self.cleaned_data: try: - user = User.objects.get(email = self.cleaned_data['email']) + User.objects.get(email = self.cleaned_data['email']) except User.DoesNotExist: return self.cleaned_data['email'] except User.MultipleObjectsReturned: @@ -336,7 +335,7 @@ class EditUserForm(forms.Form): return self.cleaned_data['email'] class TagFilterSelectionForm(forms.ModelForm): - tag_filter_setting = forms.ChoiceField(choices=TAG_EMAIL_FILTER_CHOICES, #imported from forum/const.py + tag_filter_setting = forms.ChoiceField(choices=const.TAG_EMAIL_FILTER_CHOICES, initial='ignored', label=_('Choose email tag filter'), widget=forms.RadioSelect) @@ -393,10 +392,10 @@ class EditUserEmailFeedsForm(forms.Form): label=_('Comments and posts mentioning me'), ) - def set_initial_values(self,user=None): - KEY_MAP = dict([(v,k) for k,v in self.FORM_TO_MODEL_MAP.iteritems()]) + 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) + settings = models.EmailFeedSetting.objects.filter(subscriber=user) initial_values = {} for setting in settings: feed_type = setting.feed_type @@ -422,8 +421,10 @@ class EditUserEmailFeedsForm(forms.Form): """ 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) + s, created = models.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: @@ -440,7 +441,7 @@ class EditUserEmailFeedsForm(forms.Form): if created: s.save() if form_field == 'individually_selected': - feed_type = ContentType.objects.get_for_model(Question) + feed_type = ContentType.objects.get_for_model(models.Question) user.followed_questions.clear() return changed @@ -455,11 +456,11 @@ class SimpleEmailSubscribeForm(forms.Form): choices=SIMPLE_SUBSCRIBE_CHOICES ) - def save(self,user=None): + 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) + email_settings_form.save(user, save_unbound=True) diff --git a/forum/importers/stackexchange/management/commands/load_stackexchange.py b/forum/importers/stackexchange/management/commands/load_stackexchange.py index 8e5b74a1..5145035e 100644 --- a/forum/importers/stackexchange/management/commands/load_stackexchange.py +++ b/forum/importers/stackexchange/management/commands/load_stackexchange.py @@ -11,7 +11,6 @@ import forum.models as askbot import django_authopenid.models as askbot_openid import forum.importers.stackexchange.models as se from forum.forms import EditUserEmailFeedsForm -from forum.utils.html import sanitize_html from forum.conf import settings as forum_settings from django.contrib.auth.models import Message as DjangoMessage from django.utils.translation import ugettext as _ @@ -797,8 +796,4 @@ class Command(BaseCommand): form.initial['answered_by_me'] = 'd' # form.save(user=u, save_unbound=True) - - if 'u_auth' in locals(): - u_auth.user = u - u_auth.save() USER[se_u.id] = u diff --git a/forum/management/commands/base_command.py b/forum/management/commands/base_command.py index c073bf7a..46a2d7a7 100644 --- a/forum/management/commands/base_command.py +++ b/forum/management/commands/base_command.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -#encoding:utf-8 +""" #------------------------------------------------------------------------------- # Name: Award badges command # Purpose: This is a command file croning in background process regularly to @@ -11,15 +10,11 @@ # Copyright: (c) Mike 2009 # Licence: GPL V2 #------------------------------------------------------------------------------- +""" +#!/usr/bin/env python +#encoding:utf-8 -from datetime import datetime, date from django.core.management.base import NoArgsCommand -from django.db import connection -from django.shortcuts import get_object_or_404 -from django.contrib.contenttypes.models import ContentType - -from forum.models import * -from forum.const import * class BaseCommand(NoArgsCommand): def update_activities_auditted(self, cursor, activity_ids): @@ -28,8 +23,3 @@ class BaseCommand(NoArgsCommand): query = "UPDATE activity SET is_auditted = 1 WHERE id in (%s)"\ % ','.join('%s' % item for item in activity_ids) cursor.execute(query) - - - - - diff --git a/forum/management/commands/clean_award_badges.py b/forum/management/commands/clean_award_badges.py index 117e3a5f..2aab8bda 100644 --- a/forum/management/commands/clean_award_badges.py +++ b/forum/management/commands/clean_award_badges.py @@ -1,3 +1,4 @@ +""" #------------------------------------------------------------------------------- # Name: Award badges command # Purpose: This is a command file croning in background process regularly to @@ -9,14 +10,13 @@ # Copyright: (c) Mike 2009 # Licence: GPL V2 #------------------------------------------------------------------------------- +""" #!/usr/bin/env python #encoding:utf-8 from django.core.management.base import NoArgsCommand from django.db import connection -from django.shortcuts import get_object_or_404 from django.contrib.contenttypes.models import ContentType - -from forum.models import * +from forum import models class Command(NoArgsCommand): def handle_noargs(self, **options): @@ -29,18 +29,18 @@ class Command(NoArgsCommand): connection.close() def clean_awards(self): - Award.objects.all().delete() + models.Award.objects.all().delete() - award_type =ContentType.objects.get_for_model(Award) - Activity.objects.filter(content_type=award_type).delete() + award_type =ContentType.objects.get_for_model(models.Award) + models.Activity.objects.filter(content_type=award_type).delete() - for user in User.objects.all(): + for user in models.User.objects.all(): user.gold = 0 user.silver = 0 user.bronze = 0 user.save() - for badge in Badge.objects.all(): + for badge in models.Badge.objects.all(): badge.awarded_count = 0 badge.save() @@ -56,4 +56,4 @@ def main(): pass if __name__ == '__main__': - main()
\ No newline at end of file + main() diff --git a/forum/management/commands/multi_award_badges.py b/forum/management/commands/multi_award_badges.py index 8d8e0065..4361713e 100644 --- a/forum/management/commands/multi_award_badges.py +++ b/forum/management/commands/multi_award_badges.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -#encoding:utf-8 +""" #------------------------------------------------------------------------------- # Name: Award badges command # Purpose: This is a command file croning in background process regularly to @@ -11,16 +10,17 @@ # Copyright: (c) Mike 2009 # Licence: GPL V2 #------------------------------------------------------------------------------- +""" +#!/usr/bin/env python +#encoding:utf-8 -from datetime import datetime, date -from django.core.management.base import NoArgsCommand from django.db import connection from django.shortcuts import get_object_or_404 from django.contrib.contenttypes.models import ContentType -from forum.models import Badge, User, Award, Question, Answer +from forum.models import Badge, User, Award, Question, Answer, Tag from forum import const -from base_command import BaseCommand +from forum.management.commands.base_command BaseCommand class Command(BaseCommand): def handle_noargs(self, **options): diff --git a/forum/management/commands/once_award_badges.py b/forum/management/commands/once_award_badges.py index 1d403da5..ea04c11d 100644 --- a/forum/management/commands/once_award_badges.py +++ b/forum/management/commands/once_award_badges.py @@ -192,12 +192,12 @@ class Command(BaseCommand): """ For user asked question and got first upvote, we award him following badge: """ - query = "SELECT act.user_id, q.vote_up_count, act.object_id FROM " - "activity act, question q WHERE act.activity_type = %s AND " - "act.object_id = q.id AND " - "act.user_id NOT IN " - "(SELECT distinct user_id FROM award WHERE badge_id = %s)"\ - % (const.TYPE_ACTIVITY_ASK_QUESTION, 13) + query = ('SELECT act.user_id, q.vote_up_count, act.object_id FROM ' \ + + 'activity act, question q WHERE act.activity_type = %s AND ' \ + + 'act.object_id = q.id AND ' \ + + 'act.user_id NOT IN ' \ + + '(SELECT distinct user_id FROM award WHERE badge_id = %s)') \ + % (const.TYPE_ACTIVITY_ASK_QUESTION, 13) cursor = connection.cursor() try: cursor.execute(query) @@ -224,12 +224,12 @@ class Command(BaseCommand): (15, '教师', 3, '教师', '第一次回答问题并且得到一个以上赞成票', 0, 0), """ - query = "SELECT act.user_id, a.vote_up_count, act.object_id FROM " - "activity act, answer a WHERE act.activity_type = %s AND " - "act.object_id = a.id AND " - "act.user_id NOT IN " - "(SELECT distinct user_id FROM award WHERE badge_id = %s)"\ - % (const.TYPE_ACTIVITY_ANSWER, 15) + query = ("SELECT act.user_id, a.vote_up_count, act.object_id FROM "\ + + "activity act, answer a WHERE act.activity_type = %s AND "\ + + "act.object_id = a.id AND "\ + + "act.user_id NOT IN "\ + + "(SELECT distinct user_id FROM award WHERE badge_id = %s)") \ + % (const.TYPE_ACTIVITY_ANSWER, 15) cursor = connection.cursor() try: cursor.execute(query) @@ -254,13 +254,13 @@ class Command(BaseCommand): """ (32, '学问家', 2, '学问家', '第一次回答被投赞成票10次以上', 0, 0) """ - query = "SELECT act.user_id, act.object_id FROM " - "activity act, answer a WHERE act.object_id = a.id AND " - "act.activity_type = %s AND " - "a.vote_up_count >= 10 AND " - "act.user_id NOT IN " - "(SELECT user_id FROM award WHERE badge_id = %s)"\ - % (const.TYPE_ACTIVITY_ANSWER, 32) + query = ("SELECT act.user_id, act.object_id FROM " \ + + "activity act, answer a WHERE act.object_id = a.id AND "\ + + "act.activity_type = %s AND "\ + + "a.vote_up_count >= 10 AND "\ + + "act.user_id NOT IN "\ + + "(SELECT user_id FROM award WHERE badge_id = %s)") \ + % (const.TYPE_ACTIVITY_ANSWER, 32) cursor = connection.cursor() try: cursor.execute(query) @@ -284,16 +284,16 @@ class Command(BaseCommand): """ (26, '优秀市民', 2, '优秀市民', '投票300次以上', 0, 0) """ - query = "SELECT count(*) vote_count, user_id FROM activity WHERE " - "activity_type = %s OR " - "activity_type = %s AND " - "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " - "GROUP BY user_id HAVING vote_count >= 300"\ - % ( - const.TYPE_ACTIVITY_VOTE_UP, - const.TYPE_ACTIVITY_VOTE_DOWN, - 2 - ) + query = ("SELECT count(*) vote_count, user_id FROM activity WHERE " \ + + "activity_type = %s OR " \ + + "activity_type = %s AND " \ + + "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \ + + "GROUP BY user_id HAVING vote_count >= 300") \ + % ( + const.TYPE_ACTIVITY_VOTE_UP, + const.TYPE_ACTIVITY_VOTE_DOWN, + 2 + ) self.__award_for_count_num(query, 26) @@ -301,16 +301,16 @@ class Command(BaseCommand): """ (27, '编辑主任', 2, '编辑主任', '编辑了100个帖子', 0, 0) """ - query = "SELECT count(*) vote_count, user_id FROM activity WHERE " - "activity_type = %s OR " - "activity_type = %s AND " - "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " - "GROUP BY user_id HAVING vote_count >= 100"\ - % ( - const.TYPE_ACTIVITY_UPDATE_QUESTION, - const.TYPE_ACTIVITY_UPDATE_ANSWER, - 27 - ) + query = ("SELECT count(*) vote_count, user_id FROM activity WHERE " \ + + "activity_type = %s OR " \ + + "activity_type = %s AND " \ + + "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \ + + "GROUP BY user_id HAVING vote_count >= 100" )\ + % ( + const.TYPE_ACTIVITY_UPDATE_QUESTION, + const.TYPE_ACTIVITY_UPDATE_ANSWER, + 27 + ) self.__award_for_count_num(query, 27) @@ -318,16 +318,16 @@ class Command(BaseCommand): """ (5, '评论家', 3, '评论家', '评论10次以上', 0, 0), """ - query = "SELECT count(*) vote_count, user_id FROM activity WHERE " - "activity_type = %s OR " - "activity_type = %s AND " - "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " - "GROUP BY user_id HAVING vote_count >= 10"\ - % ( - const.TYPE_ACTIVITY_COMMENT_QUESTION, - const.TYPE_ACTIVITY_COMMENT_ANSWER, - 5 - ) + query = ("SELECT count(*) vote_count, user_id FROM activity WHERE " \ + + "activity_type = %s OR " \ + + "activity_type = %s AND " \ + + "user_id NOT IN (SELECT user_id FROM award WHERE badge_id = %s) " \ + + "GROUP BY user_id HAVING vote_count >= 10" )\ + % ( + const.TYPE_ACTIVITY_COMMENT_QUESTION, + const.TYPE_ACTIVITY_COMMENT_ANSWER, + 5 + ) self.__award_for_count_num(query, 5) def __award_for_count_num(self, query, badge): diff --git a/forum/management/commands/send_email_alerts.py b/forum/management/commands/send_email_alerts.py index ccda1081..cd434a34 100644 --- a/forum/management/commands/send_email_alerts.py +++ b/forum/management/commands/send_email_alerts.py @@ -10,7 +10,6 @@ from django.utils.translation import ungettext import datetime from django.conf import settings from forum.conf import settings as forum_settings -import logging from django.utils.datastructures import SortedDict from django.contrib.contenttypes.models import ContentType from forum import const @@ -247,11 +246,11 @@ class Command(NoArgsCommand): q_mentions_id = [q.id for q in mentions.get_all_origin_posts()] - q_mentions_A = Q_set_A.filter(id__in, q_mentions_id) + q_mentions_A = Q_set_A.filter(id__in = q_mentions_id) q_mentions_A.cutoff_time = cutoff_time extend_question_list(q_mentions_A, q_list, add_mention=True) - q_mentions_B = Q_set_B.filter(id__in, q_mentions_id) + q_mentions_B = Q_set_B.filter(id__in = q_mentions_id) q_mentions_B.cutoff_time = cutoff_time extend_question_list(q_mentions_B, q_list, add_mention=True) @@ -301,7 +300,7 @@ class Command(NoArgsCommand): content_object=q, activity_type=EMAIL_UPDATE_ACTIVITY ) - emailed_at = datetime.datetime(1970,1,1)#long time ago + emailed_at = datetime.datetime(1970, 1, 1)#long time ago except Activity.MultipleObjectsReturned: raise Exception( 'server error - multiple question email activities ' @@ -341,8 +340,10 @@ class Command(NoArgsCommand): new_ans = new_ans.exclude(author=user) meta_data['new_ans'] = len(new_ans) - ans_rev = AnswerRevision.objects.filter(answer__question=q,\ - revised_at__gt=emailed_at) + ans_rev = AnswerRevision.objects.filter( + answer__question=q, + revised_at__gt=emailed_at + ) ans_rev = ans_rev.exclude(author=user) meta_data['ans_rev'] = len(ans_rev) @@ -458,6 +459,11 @@ class Command(NoArgsCommand): msg.content_subtype = 'html' msg.send() else: - msg2 = EmailMessage(subject, text, settings.DEFAULT_FROM_EMAIL, ['your@email.com']) + msg2 = EmailMessage( + subject, + text, + settings.DEFAULT_FROM_EMAIL, + ['your@email.com'] + ) msg2.content_subtype = 'html' msg2.send() diff --git a/forum/management/commands/subscribe_everyone.py b/forum/management/commands/subscribe_everyone.py index c79528f3..fac4168f 100644 --- a/forum/management/commands/subscribe_everyone.py +++ b/forum/management/commands/subscribe_everyone.py @@ -1,15 +1,10 @@ from django.core.management.base import NoArgsCommand from django.db import connection -from django.db.models import Q, F -from forum.models import * +from forum.models import EmailFeedSetting, User from django.core.mail import EmailMessage -from django.utils.translation import ugettext as _ -from django.utils.translation import ungettext -import datetime -from django.conf import settings class Command(NoArgsCommand): - def handle_noargs(self,**options): + def handle_noargs(self, **options): try: try: self.subscribe_everyone() @@ -24,9 +19,15 @@ class Command(NoArgsCommand): for user in User.objects.all(): for feed_type in feed_type_info: try: - feed_setting = EmailFeedSetting.objects.get(subscriber=user,feed_type = feed_type[0]) + feed_setting = EmailFeedSetting.objects.get( + subscriber=user, + feed_type = feed_type[0] + ) except EmailFeedSetting.DoesNotExist: - feed_setting = EmailFeedSetting(subscriber=user,feed_type=feed_type[0]) + feed_setting = EmailFeedSetting( + subscriber=user, + feed_type=feed_type[0] + ) feed_setting.frequency = 'w' feed_setting.reported_at = None feed_setting.save() diff --git a/forum/middleware/anon_user.py b/forum/middleware/anon_user.py index 51d35fa7..24eff0a2 100644 --- a/forum/middleware/anon_user.py +++ b/forum/middleware/anon_user.py @@ -1,17 +1,13 @@ -from django.http import HttpResponseRedirect -from forum.utils.forms import get_next_url from django.utils.translation import ugettext as _ from forum.user_messages import create_message, get_and_delete_messages -from django.core.urlresolvers import reverse from forum.conf import settings as forum_settings from forum import const -import logging class AnonymousMessageManager(object): - def __init__(self,request): + def __init__(self, request): self.request = request - def create(self,message=''): - create_message(self.request,message) + def create(self, message=''): + create_message(self.request, message) def get_and_delete(self): messages = get_and_delete_messages(self.request) return messages @@ -25,12 +21,16 @@ def dummy_deepcopy(*arg): class ConnectToSessionMessagesMiddleware(object): def process_request(self, request): if not request.user.is_authenticated(): - request.user.__deepcopy__ = dummy_deepcopy #plug on deepcopy which may be called by django db "driver" - request.user.message_set = AnonymousMessageManager(request) #here request is linked to anon user - request.user.get_and_delete_messages = request.user.message_set.get_and_delete + #plug on deepcopy which may be called by django db "driver" + request.user.__deepcopy__ = dummy_deepcopy + #here request is linked to anon user + request.user.message_set = AnonymousMessageManager(request) + request.user.get_and_delete_messages = \ + request.user.message_set.get_and_delete #also set the first greeting one time per session only if 'greeting_set' not in request.session: request.session['greeting_set'] = True - msg = _(const.GREETING_FOR_ANONYMOUS_USER) % forum_settings.GREETING_URL + msg = _(const.GREETING_FOR_ANONYMOUS_USER) \ + % forum_settings.GREETING_URL request.user.message_set.create(message=msg) diff --git a/forum/middleware/pagesize.py b/forum/middleware/pagesize.py index 39b9d35b..154c112f 100644 --- a/forum/middleware/pagesize.py +++ b/forum/middleware/pagesize.py @@ -1,19 +1,23 @@ +import logging +import traceback +import sys # used in questions QUESTIONS_PAGE_SIZE = 10 class QuestionsPageSizeMiddleware(object): def process_request(self, request): - # Set flag to False by default. If it is equal to True, then need to be saved. + # Set flag to False by default. If it is True, then need to be saved. page_size_changed = False # get page_size from session, if failed then get default value user_page_size = request.session.get("page_size", QUESTIONS_PAGE_SIZE) # set page_size equal to logon user specified value in database - if request.user.is_authenticated() and request.user.questions_per_page > 0: + if request.user.is_authenticated() \ + and request.user.questions_per_page > 0: user_page_size = request.user.questions_per_page try: # get new page_size from UI selection page_size = int(request.GET.get('page_size', user_page_size)) - if page_size <> user_page_size: + if page_size != user_page_size: page_size_changed = True except ValueError: @@ -28,9 +32,6 @@ class QuestionsPageSizeMiddleware(object): # put page_size into session request.session["page_size"] = page_size - def process_exception(self,request,exception): - import logging - import traceback - import sys + def process_exception(self, request, exception): exc_type, exc_value, exc_traceback = sys.exc_info() logging.debug(''.join(traceback.format_tb(exc_traceback))) diff --git a/forum/middleware/view_log.py b/forum/middleware/view_log.py index bf0b9fbb..f6112547 100644 --- a/forum/middleware/view_log.py +++ b/forum/middleware/view_log.py @@ -4,7 +4,7 @@ from forum.views.readers import questions as questions_view from forum.views.commands import vote from django.views.static import serve from forum.views.writers import delete_comment, question_comments, answer_comments -from forum.views.readers import question, question_revisions, answer_revisions +from forum.views.readers import question_revisions, answer_revisions #todo: the list is getting bigger and bigger - maybe there is a better way to #trigger reset of sarch state? @@ -19,8 +19,6 @@ class ViewLog(object): def __init__(self): self.views = [] self.depth = 3 #todo maybe move this to const.py - def set_current(self, view_name): - thi def get_previous(self, num): if num > self.depth - 1: diff --git a/forum/models/__init__.py b/forum/models/__init__.py index 3b4fd8c0..2df44bcc 100644 --- a/forum/models/__init__.py +++ b/forum/models/__init__.py @@ -1,11 +1,7 @@ -import signals -from question import Question ,QuestionRevision, QuestionView, AnonymousQuestion, FavoriteQuestion -from answer import Answer, AnonymousAnswer, AnswerRevision -from tag import Tag, MarkedTag -from meta import Vote, Comment, FlaggedItem -from user import Activity, ValidationHash, EmailFeedSetting -#from user import AuthKeyUserAssociation -from repute import Badge, Award, Repute +import logging +import re +import hashlib +import datetime from django.core.urlresolvers import reverse from django.core.mail import EmailMessage from forum.search.indexer import create_fulltext_indexes @@ -17,15 +13,19 @@ from django.template.defaultfilters import slugify from django.utils.safestring import mark_safe from django.db import models from django.conf import settings as django_settings -from forum import const -import logging -import re -import hashlib - -import datetime from django.contrib.contenttypes.models import ContentType - -#todo: must go after signals +from forum import const +from forum.models.question import Question, QuestionRevision +from forum.models.question import QuestionView, AnonymousQuestion +from forum.models.question import FavoriteQuestion +from forum.models.answer import Answer, AnonymousAnswer, AnswerRevision +from forum.models.tag import Tag, MarkedTag +from forum.models.meta import Vote, Comment, FlaggedItem +from forum.models.user import Activity, ValidationHash, EmailFeedSetting +from forum.models import signals +#from user import AuthKeyUserAssociation +from forum.models.repute import Badge, Award, Repute +from forum.conf import settings as forum_settings from forum import auth User.add_to_class('is_approved', models.BooleanField(default=False)) @@ -186,7 +186,7 @@ def _process_vote(user, post, timestamp=None, cancel=False, vote_type=None): if cancel: auth.onDownVotedCanceled(vote, post, user, timestamp) else: - auth.onDonwVoted(vote, post, user, timestamp) + auth.onDownVoted(vote, post, user, timestamp) def upvote(self, post, timestamp=None, cancel=False): _process_vote( @@ -210,14 +210,14 @@ def accept_answer(self, answer, timestamp=None, cancel=False): else: auth.onAnswerAccept(answer, self, timestamp=timestamp) -def flag_post(self, post, timestamp=None, cancel=False): +def flag_post(user, post, timestamp=None, cancel=False): if cancel:#todo: can't unflag? return if post.flagged_items.filter(user=user).count() > 0: return else: flag = FlaggedItem( - user = self, + user = user, content_object = post, flagged_at = timestamp, ) @@ -296,7 +296,7 @@ def send_instant_notifications_about_activity_in_post( #get details about update #todo: is there a way to solve this import issue? - from forum.conf import settings as forum_settings + #from forum.conf import settings as forum_settings base_url = forum_settings.APP_URL data = { 'receiving_user': u, @@ -313,7 +313,8 @@ def send_instant_notifications_about_activity_in_post( } #send update subject = _('email update message subject') - text = format_instant_notification_body(template, data) + text = format_instant_notification_body( + ) msg = EmailMessage(subject, text, django_settings.DEFAULT_FROM_EMAIL, [u.email]) print 'sending email to %s' % u.email print 'subject: %s' % subject @@ -337,29 +338,8 @@ def record_ask_event(instance, created, **kwargs): ) activity.save() -#todo: translate this -record_answer_event_re = re.compile("You have received (a|\d+) .*new response.*") def record_answer_event(instance, created, **kwargs): if created: - q_author = instance.question.author - found_match = False - for m in q_author.message_set.all(): - match = record_answer_event_re.search(m.message) - if match: - found_match = True - try: - cnt = int(match.group(1)) - except: - cnt = 1 - m.message = u"You have received %d <a href=\"%s?sort=responses\">new responses</a>."\ - % (cnt+1, q_author.get_profile_url()) - m.save() - break - if not found_match: - msg = u"You have received a <a href=\"%s?sort=responses\">new response</a>."\ - % q_author.get_profile_url() - q_author.message_set.create(message=msg) - activity = Activity( user = instance.author, active_at = instance.added_at, @@ -376,15 +356,26 @@ def record_answer_event(instance, created, **kwargs): #todo: change to more general post_update_activity -def record_post_update_activity(post, newly_mentioned_users, **kwargs): +def record_post_update_activity( + post, + newly_mentioned_users = list(), + timestamp = None, + created = False, + **kwargs + ): + """called upon signal forum.models.signals.post_updated + which is sent at the end of save() method in posts + """ #todo: take into account created == True case - activity_type = post.get_updated_activity_type() + activity_type = post.get_updated_activity_type(created) + + assert(timestamp != None) #fields will depend on post type and maybe activity type #post has to be saved already, b/c Activity is in generic relation to post activity = Activity( user = post.get_last_author(), - active_at = post.added_at, + active_at = timestamp, content_object = post, activity_type = activity_type ) @@ -420,8 +411,6 @@ def record_revision_question_event(instance, created, **kwargs): instance.question.get_author_list(include_comments = True) ) - receiving_users.update( - ) for a in instance.question.answers.all(): receiving_users.update(a.get_author_list()) @@ -562,6 +551,8 @@ def record_cancel_vote(instance, **kwargs): #todo: same problem - cannot access receiving user here activity.save() +#todo: weird that there is no record delete answer or comment +#is this even necessary to keep track of? def record_delete_question(instance, delete_by, **kwargs): """ when user deleted the question @@ -634,7 +625,7 @@ def record_user_full_updated(instance, **kwargs): def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs): aq_list = AnonymousQuestion.objects.filter(session_key = session_key) aa_list = AnonymousAnswer.objects.filter(session_key = session_key) - from forum.conf import settings as forum_settings + #from forum.conf import settings as forum_settings if forum_settings.EMAIL_VALIDATION == True:#add user to the record for aq in aq_list: aq.author = user @@ -652,7 +643,6 @@ def post_stored_anonymous_content(sender,user,session_key,signal,*args,**kwargs) #signal for User model save changes django_signals.pre_save.connect(calculate_gravatar_hash, sender=User) django_signals.post_save.connect(record_ask_event, sender=Question) -django_signals.post_save.connect(record_answer_event, sender=Answer) django_signals.post_save.connect(record_revision_question_event, sender=QuestionRevision) django_signals.post_save.connect(record_revision_answer_event, sender=AnswerRevision) django_signals.post_save.connect(record_award_event, sender=Award) @@ -675,6 +665,10 @@ signals.post_updated.connect( record_post_update_activity, sender=Comment ) +signals.post_updated.connect( + record_post_update_activity, + sender=Answer + ) #post_syncdb.connect(create_fulltext_indexes) #todo: wtf??? what is x=x about? diff --git a/forum/models/answer.py b/forum/models/answer.py index 3f52b4ab..4b35fd31 100644 --- a/forum/models/answer.py +++ b/forum/models/answer.py @@ -1,18 +1,15 @@ -from base import AnonymousContent, ContentRevision, DeletableContent -from content import Content -#todo: take care of copy-paste markdowner stuff maybe make html automatic field? -from forum import const -from markdown2 import Markdown -from django.utils.html import strip_tags -from forum.utils.html import sanitize_html +import datetime from django.db import models from django.utils.http import urlquote as django_urlquote from django.template.defaultfilters import slugify from django.core.urlresolvers import reverse -import datetime -markdowner = Markdown(html4tags=True) +from forum.models.base import AnonymousContent, DeletableContent +from forum.models.base import ContentRevision +from forum.models.base import save_post, parse_post_text +from forum.models import content +from forum.models.question import Question +from forum import const -from question import Question class AnswerManager(models.Manager): def create_new(self, question=None, author=None, added_at=None, wiki=False, text='', email_notify=False): @@ -22,7 +19,7 @@ class AnswerManager(models.Manager): added_at = added_at, wiki = wiki, text = text, - html = sanitize_html(markdowner.convert(text)), + #.html field is denormalized by the save() call ) if answer.wiki: answer.last_edited_by = answer.author @@ -84,16 +81,19 @@ class AnswerManager(models.Manager): # cursor.execute(self.GET_ANSWERS_FROM_USER_QUESTIONS, [user_id, user_id]) # return cursor.fetchall() -class Answer(Content, DeletableContent): +class Answer(content.Content, DeletableContent): question = models.ForeignKey('Question', related_name='answers') accepted = models.BooleanField(default=False) accepted_at = models.DateTimeField(null=True, blank=True) objects = AnswerManager() - class Meta(Content.Meta): + class Meta(content.Content.Meta): db_table = u'answer' + save = save_post + parse = parse_post_text + def apply_edit(self, edited_at=None, edited_by=None, text=None, comment=None, wiki=False): if text is None: @@ -105,7 +105,7 @@ class Answer(Content, DeletableContent): self.last_edited_at = edited_at self.last_edited_by = edited_by - self.html = sanitize_html(markdowner.convert(text)) + #self.html is denormalized in save() self.text = text #todo: bug wiki has no effect here self.save() diff --git a/forum/models/base.py b/forum/models/base.py index 110c0deb..1b3a4c2a 100644 --- a/forum/models/base.py +++ b/forum/models/base.py @@ -1,99 +1,143 @@ import datetime -import hashlib -from urllib import quote_plus, urlencode from django.db import models -from django.utils.http import urlquote as django_urlquote from django.utils.html import strip_tags -from django.core.urlresolvers import reverse from django.contrib.auth.models import User from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType -from django.utils.translation import ugettext as _ from django.contrib.sitemaps import ping_google -import django.dispatch -from django.conf import settings +#todo: maybe merge forum.utils.markup and forum.utils.html from forum.utils import markup +from forum.utils.html import sanitize_html from django.utils import html import logging +from markdown2 import Markdown + +markdowner = Markdown(html4tags=True) #todo: following methods belong to a future common post class -def render_post_text_and_get_newly_mentioned_users(post, - urlize_content = False): +def parse_post_text(post): + """typically post has a field to store raw source text + in comment it is called .comment, in Question and Answer it is + called .text + also there is another field called .html (consistent across models) + so the goal of this function is to render raw text into .html + and extract any metadata given stored in source (currently + this metadata is limited by twitter style @mentions + but there may be more in the future + + so really it should be renamed into ..._and_get_meta_data + """ text = post.get_text() - if urlize_content: + if post._urlize: text = html.urlize(text) - if '@' not in text: - post.html = text - return list() - - from forum.models.user import Activity - - mentioned_by = post.get_last_author() - - op = post.get_origin_post() - anticipated_authors = op.get_author_list( include_comments = True, recursive = True ) - - extra_name_seeds = markup.extract_mentioned_name_seeds(text) - - extra_authors = set() - for name_seed in extra_name_seeds: - extra_authors.update(User.objects.filter(username__startswith = name_seed)) + if post._use_markdown: + text = sanitize_html(markdowner.convert(text)) - #it is important to preserve order here so that authors of post get mentioned first - anticipated_authors += list(extra_authors) + #todo, add markdown parser call conditional on + #post.use_markdown flag + post_html = text + mentioned_authors = list() + removed_mentions = list() + if '@' in text: + from forum.models.user import Activity - mentioned_authors, post.html = markup.mentionize_text(text, anticipated_authors) + mentioned_by = post.get_last_author() - #maybe delete some previous mentions - if post.id != None: - #only look for previous mentions if post was already saved before - prev_mention_qs = Activity.objects.get_mentions( - mentioned_in = post + op = post.get_origin_post() + anticipated_authors = op.get_author_list( + include_comments = True, + recursive = True ) - new_set = set(mentioned_authors) - for mention in prev_mention_qs: - delta_set = set(mention.receiving_users.all()) - new_set - if not delta_set: - mention.delete() - new_set -= delta_set - mentioned_authors = list(new_set) + extra_name_seeds = markup.extract_mentioned_name_seeds(text) - return mentioned_authors + extra_authors = set() + for name_seed in extra_name_seeds: + extra_authors.update(User.objects.filter( + username__startswith = name_seed + ) + ) -def save_content(self, urlize_content = False, **kwargs): + #it is important to preserve order here so that authors of post + #get mentioned first + anticipated_authors += list(extra_authors) + + mentioned_authors, post_html = markup.mentionize_text( + text, + anticipated_authors + ) + + #find mentions that were removed and identify any previously + #entered mentions so that we can send alerts on only new ones + if post.pk is not None: + #only look for previous mentions if post was already saved before + prev_mention_qs = Activity.objects.get_mentions( + mentioned_in = post + ) + new_set = set(mentioned_authors) + for prev_mention in prev_mention_qs: + + user = prev_mention.get_mentioned_user() + if user in new_set: + #don't report mention twice + new_set.remove(user) + else: + removed_mentions.append(prev_mention) + mentioned_authors = list(new_set) + + data = { + 'html': post_html, + 'newly_mentioned_users': mentioned_authors, + 'removed_mentions': removed_mentions, + } + return data + +def save_post(post, **kwargs): """generic save method to use with posts """ - new_mentions = self._render_text_and_get_newly_mentioned_users( - urlize_content - ) + data = post.parse() - from forum.models.user import Activity + post.html = data['html'] + newly_mentioned_users = data['newly_mentioned_users'] + removed_mentions = data['removed_mentions'] - #this save must precede saving the mention activity - super(self.__class__, self).save(**kwargs) + #a hack allowing to save denormalized .summary field for questions + if hasattr(post, 'summary'): + post.summary = strip_tags(post.html)[:120] + + #delete removed mentions + for rm in removed_mentions: + rm.delete() - post_author = self.get_last_author() + created = post.pk is None - for u in new_mentions: + #this save must precede saving the mention activity + #because generic relation needs primary key of the related object + super(post.__class__, post).save(**kwargs) + last_author = post.get_last_author() + + #create new mentions + for u in newly_mentioned_users: + from forum.models.user import Activity Activity.objects.create_new_mention( mentioned_whom = u, - mentioned_in = self, - mentioned_by = post_author + mentioned_in = post, + mentioned_by = last_author ) - #todo: this is handled in signal because models for posts #are too spread out from forum.models import signals signals.post_updated.send( - post = self, - newly_mentioned_users = new_mentions, - sender = self.__class__ + post = post, + newly_mentioned_users = newly_mentioned_users, + timestamp = post.get_time_of_last_edit(), + created = created, + sender = post.__class__ ) try: diff --git a/forum/models/content.py b/forum/models/content.py index 95064692..5f144f1e 100644 --- a/forum/models/content.py +++ b/forum/models/content.py @@ -1,9 +1,10 @@ +import datetime +import logging from django.contrib.auth.models import User from django.contrib.contenttypes import generic +from django.contrib.sitemaps import ping_google from django.db import models -from meta import Comment, Vote, FlaggedItem -import datetime -import logging +from forum.models.meta import Comment, Vote, FlaggedItem class Content(models.Model): """ @@ -35,6 +36,9 @@ class Content(models.Model): votes = generic.GenericRelation(Vote) flagged_items = generic.GenericRelation(FlaggedItem) + _use_markdown = True + _urlize = False + class Meta: abstract = True app_label = 'forum' @@ -80,6 +84,12 @@ class Content(models.Model): def get_last_author(self): return self.last_edited_by + def get_time_of_last_edit(self): + if self.last_edited_at: + return self.last_edited_at + else: + return self.added_at + def get_author_list(self, include_comments = False, recursive = False, exclude_list = None): authors = set() authors.update([r.author for r in self.revisions.all()]) @@ -108,7 +118,11 @@ class Content(models.Model): return True else: - raise Exception('unexpected User.tag_filter_setting %' % self.tag_filter_setting) + raise ValueError( + 'unexpected User.tag_filter_setting %s' \ + % self.tag_filter_setting + ) + def post_get_last_update_info(self):#todo: rename this subroutine when = self.added_at who = self.author diff --git a/forum/models/meta.py b/forum/models/meta.py index d0530694..5f36d76e 100644 --- a/forum/models/meta.py +++ b/forum/models/meta.py @@ -1,9 +1,7 @@ +import datetime from django.db import models -from base import MetaContent, UserContent -from base import render_post_text_and_get_newly_mentioned_users -from base import save_content from forum import const -import datetime +from forum.models import base class VoteManager(models.Manager): def get_up_vote_count_from_user(self, user): @@ -26,7 +24,7 @@ class VoteManager(models.Manager): return 0 -class Vote(MetaContent, UserContent): +class Vote(base.MetaContent, base.UserContent): VOTE_UP = +1 VOTE_DOWN = -1 VOTE_CHOICES = ( @@ -39,7 +37,7 @@ class Vote(MetaContent, UserContent): objects = VoteManager() - class Meta(MetaContent.Meta): + class Meta(base.MetaContent.Meta): unique_together = ('content_type', 'object_id', 'user') db_table = u'vote' @@ -65,25 +63,28 @@ class FlaggedItemManager(models.Manager): else: return 0 -class FlaggedItem(MetaContent, UserContent): +class FlaggedItem(base.MetaContent, base.UserContent): """A flag on a Question or Answer indicating offensive content.""" flagged_at = models.DateTimeField(default=datetime.datetime.now) objects = FlaggedItemManager() - class Meta(MetaContent.Meta): + class Meta(base.MetaContent.Meta): unique_together = ('content_type', 'object_id', 'user') db_table = u'flagged_item' def __unicode__(self): return '[%s] flagged at %s' %(self.user, self.flagged_at) -class Comment(MetaContent, UserContent): +class Comment(base.MetaContent, base.UserContent): comment = models.CharField(max_length = const.COMMENT_HARD_MAX_LENGTH) added_at = models.DateTimeField(default = datetime.datetime.now) html = models.CharField(max_length = const.COMMENT_HARD_MAX_LENGTH, default='') - class Meta(MetaContent.Meta): + _urlize = True + _use_markdown = False + + class Meta(base.MetaContent.Meta): ordering = ('-added_at',) db_table = u'comment' @@ -97,15 +98,13 @@ class Comment(MetaContent, UserContent): def set_text(self, text): self.comment = text - _render_text_and_get_newly_mentioned_users = \ - render_post_text_and_get_newly_mentioned_users - - _save = save_content + def parse(self): + return base.parse_post_text(self) def save(self,**kwargs): - self._save(urlize_content = True) + base.save_post(self) - def get_updated_activity_type(self): + def get_updated_activity_type(self, created = False): if self.content_object.__class__.__name__ == 'Question': return const.TYPE_ACTIVITY_COMMENT_QUESTION elif self.content_object.__class__.__name__ == 'Answer': @@ -123,6 +122,9 @@ class Comment(MetaContent, UserContent): users -= set([self.user])#remove activity user return list(users) + def get_time_of_last_edit(self): + return self.added_at + def delete(self, **kwargs): #todo: not very good import in models of other models #todo: potentially a circular import diff --git a/forum/models/question.py b/forum/models/question.py index 6a36e0aa..0d63426d 100644 --- a/forum/models/question.py +++ b/forum/models/question.py @@ -1,23 +1,20 @@ -from base import DeletableContent, AnonymousContent, ContentRevision -from content import Content -from forum.models import signals -from tag import Tag -from forum import const -from forum.utils.html import sanitize_html -from markdown2 import Markdown -from django.utils.html import strip_tags +import logging import datetime from django.conf import settings from django.utils.datastructures import SortedDict -from forum.models.tag import MarkedTag from django.db import models from django.contrib.auth.models import User -from django.utils.http import urlquote as django_urlquote +from django.utils.http import urlquote as django_urlquote from django.template.defaultfilters import slugify from django.core.urlresolvers import reverse - -markdowner = Markdown(html4tags=True) - +from django.contrib.sitemaps import ping_google +from django.utils.translation import ugettext as _ +from forum.models.tag import Tag, MarkedTag +from forum.models import signals +from forum.models.base import AnonymousContent, DeletableContent, ContentRevision +from forum.models.base import save_post, parse_post_text +from forum.models import content +from forum import const from forum.utils.lists import LazyList #todo: too bad keys are duplicated see const sort methods @@ -35,8 +32,7 @@ QUESTION_ORDER_BY_MAP = { class QuestionManager(models.Manager): def create_new(self, title=None,author=None,added_at=None, wiki=False,tagnames=None, text=None): - html = sanitize_html(markdowner.convert(text)) - summary = strip_tags(html)[:120] + question = Question( title = title, author = author, @@ -45,11 +41,15 @@ class QuestionManager(models.Manager): last_activity_by = author, wiki = wiki, tagnames = tagnames, - html = html, + #html field is denormalized in .save() call text = text, - summary = summary + #summary field is denormalized in .save() call ) if question.wiki: + #todo: this is confusing - last_edited_at field + #is used as an indicator whether question has been edited + #in template forum/skins/default/templates/post_contributor_info.html + #but in principle, post creation should count as edit as well question.last_edited_by = question.author question.last_edited_at = added_at question.wikified_at = added_at @@ -233,7 +233,7 @@ class QuestionManager(models.Manager): return False - #todo: why not make this into a method of class Question? + #todo: why not make this into a method of Question class? # also it is actually strange - why do we need the answer_count # field if the count depends on who is requesting this? def update_answer_count(self, question): @@ -244,7 +244,7 @@ class QuestionManager(models.Manager): # for some reasons, this Answer class failed to be imported, # although we have imported all classes from models on top. - from answer import Answer + from forum.models.answer import Answer self.filter(id=question.id).update( answer_count=Answer.objects.get_answers_from_question(question).filter(deleted=False).count()) @@ -285,7 +285,7 @@ class QuestionManager(models.Manager): return LazyList(get_data) -class Question(Content, DeletableContent): +class Question(content.Content, DeletableContent): title = models.CharField(max_length=300) tags = models.ManyToManyField('Tag', related_name='questions') answer_accepted = models.BooleanField(default=False) @@ -312,9 +312,11 @@ class Question(Content, DeletableContent): objects = QuestionManager() - class Meta(Content.Meta): + class Meta(content.Content.Meta): db_table = u'question' + parse = parse_post_text + def delete(self): super(Question, self).delete() try: @@ -335,8 +337,8 @@ class Question(Content, DeletableContent): # Update the Question's tag associations signals.tags_updated = self.objects.update_tags( self, - form.cleaned_data['tags'], - request.user + tagnames, + retagged_by ) # Create a new revision @@ -351,7 +353,7 @@ class Question(Content, DeletableContent): text = latest_revision.text ) # send tags updated singal - signals.tags_updated.send(sender=question.__class__, question=self) + signals.tags_updated.send(sender=Question, question=self) def get_origin_post(self): return self @@ -374,10 +376,6 @@ class Question(Content, DeletableContent): if edited_at is None: edited_at = datetime.datetime.now() - #todo: have this copy-paste in few places - html = sanitize_html(markdowner.convert(text)) - question_summary = strip_tags(html)[:120] - # Update the Question itself self.title = title self.last_edited_at = edited_at @@ -385,8 +383,6 @@ class Question(Content, DeletableContent): self.last_edited_by = edited_by self.last_activity_by = edited_by self.tagnames = tags - self.summary = question_summary - self.html = html self.text = text #wiki is an eternal trap whence there is no exit @@ -436,13 +432,15 @@ class Question(Content, DeletableContent): This is required as we're using ``tagnames`` as the sole means of adding and editing tags. """ - initial_addition = (self.id is None) - - super(Question, self).save(**kwargs) + initial_addition = (self.pk is None) + + save_post(self, **kwargs) if initial_addition: - tags = Tag.objects.get_or_create_multiple(self.tagname_list(), - self.author) + tags = Tag.objects.get_or_create_multiple( + self.tagname_list(), + self.author + ) self.tags.add(*tags) Tag.objects.update_use_counts(tags) @@ -454,7 +452,10 @@ class Question(Content, DeletableContent): return u','.join([unicode(tag) for tag in self.tagname_list()]) def get_absolute_url(self): - return '%s%s' % (reverse('question', args=[self.id]), django_urlquote(slugify(self.title))) + return '%s%s' % ( + reverse('question', args=[self.id]), + django_urlquote(slugify(self.title)) + ) def has_favorite_by_user(self, user): if not user.is_authenticated(): @@ -463,7 +464,7 @@ class Question(Content, DeletableContent): return FavoriteQuestion.objects.filter(question=self, user=user).count() > 0 def get_answer_count_by_user(self, user_id): - from answer import Answer + from forum.models.answer import Answer query_set = Answer.objects.filter(author__id=user_id) return query_set.filter(question=self).count() @@ -523,6 +524,7 @@ class Question(Content, DeletableContent): answer_comments.append(comment) #create the report + from forum.conf import settings as forum_settings if edited or new_answers or modified_answers or answer_comments: out = [] if edited: @@ -618,5 +620,3 @@ class AnonymousQuestion(AnonymousContent): text=self.text, ) self.delete() - -from answer import Answer, AnswerManager diff --git a/forum/models/tag.py b/forum/models/tag.py index 0e585547..89f706f5 100644 --- a/forum/models/tag.py +++ b/forum/models/tag.py @@ -1,9 +1,9 @@ -from base import DeletableContent from django.db import models from django.db import connection, transaction from django.contrib.auth.models import User - from django.utils.translation import ugettext as _ +from forum.models.base import DeletableContent + class TagManager(models.Manager): UPDATE_USED_COUNTS_QUERY = ( diff --git a/forum/models/user.py b/forum/models/user.py index c05d911b..083aca79 100644 --- a/forum/models/user.py +++ b/forum/models/user.py @@ -1,18 +1,17 @@ -#todo: remove this with Django 1.2 +from hashlib import md5 +import string +from random import Random +import datetime +import logging from django.db import models +from django.db.backends.dummy.base import IntegrityError from django.contrib.contenttypes.models import ContentType -from forum.models import signals from django.contrib.contenttypes import generic from django.contrib.auth.models import User -from hashlib import md5 -import string -from random import Random +from django.utils.translation import ugettext as _ from forum import const from forum.utils import functions -import datetime -import logging -from django.utils.translation import ugettext as _ class ActivityManager(models.Manager): def get_all_origin_posts(self): @@ -80,8 +79,12 @@ class ActivityManager(models.Manager): mentioned_whom = None, mentioned_at = None, mentioned_in = None, - reported = None + reported = None, + mentioned_at__gt = None, ): + """extract mention-type activity objects + todo: implement better rich field lookups + """ kwargs = dict() @@ -90,6 +93,8 @@ class ActivityManager(models.Manager): if mentioned_at: #todo: handle cases with rich lookups here like __lt kwargs['active_at'] = mentioned_at + elif mentioned_at__gt: + kwargs['active_at__gt'] = mentioned_at__gt if mentioned_by: kwargs['user'] = mentioned_by @@ -139,6 +144,12 @@ class Activity(models.Model): app_label = 'forum' db_table = u'activity' + def get_mentioned_user(self): + assert(self.activity_type == const.TYPE_ACTIVITY_MENTION) + user_qs = self.receiving_users.all() + assert(len(user_qs) == 1) + return user_qs[0] + class EmailFeedSettingManager(models.Manager): def exists_match_to_post_and_subscriber( diff --git a/forum/search/state_manager.py b/forum/search/state_manager.py index 86cc5662..fda4bcb9 100644 --- a/forum/search/state_manager.py +++ b/forum/search/state_manager.py @@ -20,7 +20,7 @@ def some_in(what, where): class SearchState(object): def __init__(self): - self.scope= const.DEFAULT_POST_SCOPE + self.scope = const.DEFAULT_POST_SCOPE self.query = None self.tags = None self.author = None @@ -64,76 +64,76 @@ class SearchState(object): setattr(self, key, new_value) self.reset_page() - def relax_stickiness(self, input, view_log): + def relax_stickiness(self, input_dict, view_log): if view_log.get_previous(1) == 'questions': - if not some_in(ACTIVE_COMMANDS, input): + if not some_in(ACTIVE_COMMANDS, input_dict): self.reset() #todo also relax if 'all' scope was clicked twice - def update_from_user_input(self,input,raw_input = {}): + def update_from_user_input(self, input_dict, unprocessed_input = {}): #todo: this function will probably not #fit the case of multiple parameters entered at the same tiem - if 'start_over' in input: + if 'start_over' in input_dict: self.reset() - if 'page' in input: - self.page = input['page'] + if 'page' in input_dict: + self.page = input_dict['page'] #special case - on page flip no other input is accepted return - if 'page_size' in input: - self.update_value('page_size',input) + if 'page_size' in input_dict: + self.update_value('page_size', input_dict) self.reset_page()#todo may be smarter here - start with ~same q #same as with page - return right away return - if 'scope' in input: - if input['scope'] == 'favorite' and self.logged_in == False: + if 'scope' in input_dict: + if input_dict['scope'] == 'favorite' and self.logged_in == False: self.reset_scope() else: - self.update_value('scope',input) + self.update_value('scope', input_dict) - if 'tags' in input: + if 'tags' in input_dict: if self.tags: old_tags = self.tags.copy() - self.tags = self.tags.union(input['tags']) + self.tags = self.tags.union(input_dict['tags']) if self.tags != old_tags: self.reset_page() else: - self.tags = input['tags'] + self.tags = input_dict['tags'] #all resets just return - if 'reset_tags' in input: + if 'reset_tags' in input_dict: if self.tags: self.tags = None self.reset_page() return #todo: handle case of deleting tags one-by-one - if 'reset_author' in input: + if 'reset_author' in input_dict: if self.author: self.author = None self.reset_page() return - if 'reset_query' in input: + if 'reset_query' in input_dict: self.reset_query() return - self.update_value('author',input) + self.update_value('author', input_dict) - if 'query' in input: - self.update_value('query',input) + if 'query' in input_dict: + self.update_value('query', input_dict) self.sort = 'relevant' - elif 'search' in raw_input:#a case of use nulling search query by hand + elif 'search' in unprocessed_input:#a case of use nulling search query by hand self.reset_query() return - if 'sort' in input: - if input['sort'] == 'relevant' and self.query is None: + if 'sort' in input_dict: + if input_dict['sort'] == 'relevant' and self.query is None: self.reset_sort() else: - self.update_value('sort',input) + self.update_value('sort', input_dict) #todo: plug - mysql has no relevance sort if self.sort == 'relevant': diff --git a/forum/skins/__init__.py b/forum/skins/__init__.py index 8e5265e8..b7b222a2 100644 --- a/forum/skins/__init__.py +++ b/forum/skins/__init__.py @@ -1,9 +1,10 @@ from django.template import loader from django.template.loaders import filesystem -from django.http import HttpResponse import os.path import os import logging +from forum.conf import settings as forum_settings +from forum.skins import utils #module for skinning askbot #via ASKBOT_DEFAULT_SKIN configureation variable (not django setting) @@ -13,28 +14,21 @@ import logging #here it is ignored because it is assumed that we won't use unicode paths def load_template_source(name, dirs=None): + print 'want template %s' % name try: #todo: move this to top after splitting out get_skin_dirs() - from forum.conf import settings as forum_settings + print 'trying to import forum_settings' + print 'imported!' tname = os.path.join(forum_settings.ASKBOT_DEFAULT_SKIN,'templates',name) + print tname + print 'success' return filesystem.load_template_source(tname,dirs) except: + print 'failed' tname = os.path.join('default','templates',name) return filesystem.load_template_source(tname,dirs) load_template_source.is_usable = True -#todo: move this to skins/utils.py -#then move import forum.conf.settings to top -def get_skin_dirs(): - #todo: handle case of multiple skin directories - d = os.path.dirname - n = os.path.normpath - j = os.path.join - f = os.path.isfile - skin_dirs = [] - skin_dirs.append( n(j(d(d(__file__)), 'skins')) ) - return skin_dirs - def find_media_source(url): """returns url prefixed with the skin name of the first skin that contains the file @@ -43,20 +37,23 @@ def find_media_source(url): if file is not found - returns None and logs an error message """ + print 'trying to get source dammit' while url[0] == '/': url = url[1:] d = os.path.dirname n = os.path.normpath j = os.path.join f = os.path.isfile #todo: handles case of multiple skin directories - skins = get_skin_dirs()[0] + skins = utils.get_skin_dirs()[0] try: #todo: move this to top after splitting out get_skin_dirs() - from forum.conf import settings as forum_settings + print 'looking for the media path' media = os.path.join(skins, forum_settings.ASKBOT_DEFAULT_SKIN, url) + print 'out of dadata' assert(f(media)) use_skin = forum_settings.ASKBOT_DEFAULT_SKIN except: + print 'failed' try: media = j(skins, 'default', url) assert(f(media)) @@ -71,20 +68,3 @@ def find_media_source(url): use_skin = '' return None return use_skin + '/' + url - -def get_skin_choices(): - #todo: expand this to handle custom skin directories - dirs = get_skin_dirs() - default_dir = dirs[0] - items = os.listdir(default_dir) - skin_list = ['default'] - for i in items: - item_path = os.path.join(default_dir,i) - if not os.path.isdir(item_path): - continue - if i == 'common': - continue - if i not in skin_list: - skin_list.append(i) - - return [(i,i) for i in skin_list] diff --git a/forum/skins/utils.py b/forum/skins/utils.py new file mode 100644 index 00000000..e3f2ddc5 --- /dev/null +++ b/forum/skins/utils.py @@ -0,0 +1,29 @@ +import os + +def get_skin_dirs(): + #todo: handle case of multiple skin directories + d = os.path.dirname + n = os.path.normpath + j = os.path.join + f = os.path.isfile + skin_dirs = [] + skin_dirs.append( n(j(d(d(__file__)), 'skins')) ) + return skin_dirs + +def get_skin_choices(): + #todo: expand this to handle custom skin directories + dirs = get_skin_dirs() + default_dir = dirs[0] + items = os.listdir(default_dir) + skin_list = ['default'] + for i in items: + item_path = os.path.join(default_dir,i) + if not os.path.isdir(item_path): + continue + if i == 'common': + continue + if i not in skin_list: + skin_list.append(i) + + return [(i,i) for i in skin_list] + diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py index 39a1ded7..972e8c9e 100644 --- a/forum/templatetags/extra_tags.py +++ b/forum/templatetags/extra_tags.py @@ -1,6 +1,5 @@ import time import os -import posixpath import datetime import math import re @@ -294,7 +293,7 @@ def media(url): url = skins.find_media_source(url) if url: url = '///' + settings.FORUM_SCRIPT_ALIAS + '/m/' + url - return posixpath.normpath(url) + '?v=%d' \ + return os.path.normpath(url) + '?v=%d' \ % forum_settings.MEDIA_RESOURCE_REVISION else: return '' #todo: raise exception here? @@ -377,7 +376,7 @@ class BlockMediaUrlNode(template.Node): url = skins.find_media_source(url) url = prefix + url - out = posixpath.normpath(url) + '?v=%d' % forum_settings.MEDIA_RESOURCE_REVISION + out = os.path.normpath(url) + '?v=%d' % forum_settings.MEDIA_RESOURCE_REVISION return out.replace(' ','') @register.tag(name='blockmedia') @@ -426,41 +425,41 @@ def question_counter_widget(question): #background and foreground colors for each item (views_fg, views_bg) = colors.get_counter_colors( - view_count, - max = forum_settings.VIEW_COUNTER_EXPECTED_MAXIMUM, - zero_bg = forum_settings.COLORS_VIEW_COUNTER_EMPTY_BG, - zero_fg = forum_settings.COLORS_VIEW_COUNTER_EMPTY_FG, - min_bg = forum_settings.COLORS_VIEW_COUNTER_MIN_BG, - min_fg = forum_settings.COLORS_VIEW_COUNTER_MIN_FG, - max_bg = forum_settings.COLORS_VIEW_COUNTER_MAX_BG, - max_fg = forum_settings.COLORS_VIEW_COUNTER_MAX_FG, - ) + view_count, + counter_max = forum_settings.VIEW_COUNTER_EXPECTED_MAXIMUM, + zero_bg = forum_settings.COLORS_VIEW_COUNTER_EMPTY_BG, + zero_fg = forum_settings.COLORS_VIEW_COUNTER_EMPTY_FG, + min_bg = forum_settings.COLORS_VIEW_COUNTER_MIN_BG, + min_fg = forum_settings.COLORS_VIEW_COUNTER_MIN_FG, + max_bg = forum_settings.COLORS_VIEW_COUNTER_MAX_BG, + max_fg = forum_settings.COLORS_VIEW_COUNTER_MAX_FG, + ) (answers_fg, answers_bg) = colors.get_counter_colors( - answer_count, - max = forum_settings.ANSWER_COUNTER_EXPECTED_MAXIMUM, - zero_bg = forum_settings.COLORS_ANSWER_COUNTER_EMPTY_BG, - zero_fg = forum_settings.COLORS_ANSWER_COUNTER_EMPTY_FG, - min_bg = forum_settings.COLORS_ANSWER_COUNTER_MIN_BG, - min_fg = forum_settings.COLORS_ANSWER_COUNTER_MIN_FG, - max_bg = forum_settings.COLORS_ANSWER_COUNTER_MAX_BG, - max_fg = forum_settings.COLORS_ANSWER_COUNTER_MAX_FG, - ) + answer_count, + counter_max = forum_settings.ANSWER_COUNTER_EXPECTED_MAXIMUM, + zero_bg = forum_settings.COLORS_ANSWER_COUNTER_EMPTY_BG, + zero_fg = forum_settings.COLORS_ANSWER_COUNTER_EMPTY_FG, + min_bg = forum_settings.COLORS_ANSWER_COUNTER_MIN_BG, + min_fg = forum_settings.COLORS_ANSWER_COUNTER_MIN_FG, + max_bg = forum_settings.COLORS_ANSWER_COUNTER_MAX_BG, + max_fg = forum_settings.COLORS_ANSWER_COUNTER_MAX_FG, + ) if answer_accepted: #todo: maybe recalculate the foreground color too answers_bg = forum_settings.COLORS_ANSWER_COUNTER_ACCEPTED_BG answers_fg = forum_settings.COLORS_ANSWER_COUNTER_ACCEPTED_FG (votes_fg, votes_bg) = colors.get_counter_colors( - vote_count, - max = forum_settings.VOTE_COUNTER_EXPECTED_MAXIMUM, - zero_bg = forum_settings.COLORS_VOTE_COUNTER_EMPTY_BG, - zero_fg = forum_settings.COLORS_VOTE_COUNTER_EMPTY_FG, - min_bg = forum_settings.COLORS_VOTE_COUNTER_MIN_BG, - min_fg = forum_settings.COLORS_VOTE_COUNTER_MIN_FG, - max_bg = forum_settings.COLORS_VOTE_COUNTER_MAX_BG, - max_fg = forum_settings.COLORS_VOTE_COUNTER_MAX_FG, - ) + vote_count, + counter_max = forum_settings.VOTE_COUNTER_EXPECTED_MAXIMUM, + zero_bg = forum_settings.COLORS_VOTE_COUNTER_EMPTY_BG, + zero_fg = forum_settings.COLORS_VOTE_COUNTER_EMPTY_FG, + min_bg = forum_settings.COLORS_VOTE_COUNTER_MIN_BG, + min_fg = forum_settings.COLORS_VOTE_COUNTER_MIN_FG, + max_bg = forum_settings.COLORS_VOTE_COUNTER_MAX_BG, + max_fg = forum_settings.COLORS_VOTE_COUNTER_MAX_FG, + ) #returns a dictionary with keys like 'votes_bg', etc return locals() @@ -506,5 +505,4 @@ def ifmany(parser,token): else: false_nodelist = template.NodeList() - return IsManyNode(test_items, true_nodelist, false_nodelist) diff --git a/forum/tests.py b/forum/tests.py index dca9cfad..2096386b 100644 --- a/forum/tests.py +++ b/forum/tests.py @@ -1,14 +1,12 @@ -from django.test import Client, TestCase +from django.test import TestCase from django.contrib.auth.models import User -from forum import models -import datetime from django.template import defaultfilters from django.core.urlresolvers import reverse class AnonymousVisitorTests(TestCase): - fixtures = ['forum/fixtures/full_dump.json',] + fixtures = ['forum/fixtures/full_dump.json', ] def test_index(self): #todo: merge this with all reader url tests @@ -35,7 +33,7 @@ class AnonymousVisitorTests(TestCase): url = reverse(url_name, kwargs = kwargs) url_info = 'getting url %s' % url if data: - url_info += '?' + '&'.join(['%s=%s' % (k,v) for k,v in data.iteritems()]) + url_info += '?' + '&'.join(['%s=%s' % (k, v) for k, v in data.iteritems()]) print url_info r = self.client.get(url, data=data, follow=follow) @@ -215,8 +213,8 @@ class AnonymousVisitorTests(TestCase): status_code=200, follow=True, ) - u = User.objects.get(id=2) - name_slug = defaultfilters.slugify(u.username) + user = User.objects.get(id=2) + name_slug = defaultfilters.slugify(user.username) try_url( 'user_profile', kwargs={'id': 2, 'slug': name_slug}, diff --git a/forum/urls.py b/forum/urls.py index 944732e5..1864c608 100644 --- a/forum/urls.py +++ b/forum/urls.py @@ -1,11 +1,14 @@ +""" +askbot forum url configuraion file +""" import os.path -from django.conf.urls.defaults import * +from django.conf.urls.defaults import url, patterns, include +from django.conf.urls.defaults import handler500, handler404 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 _ -import logging admin.autodiscover() feeds = { @@ -18,83 +21,183 @@ sitemaps = { APP_PATH = os.path.dirname(__file__) urlpatterns = patterns('', url(r'^$', app.readers.index, name='index'), - url(r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}, name='sitemap'), - #(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/media/images/favicon.ico'}), - #(r'^favicon\.gif$', 'django.views.generic.simple.redirect_to', {'url': '/media/images/favicon.gif'}), - url(r'^m/(?P<path>.*)$', 'django.views.static.serve', + url( + r'^sitemap.xml$', + 'django.contrib.sitemaps.views.sitemap', + {'sitemaps': sitemaps}, + name='sitemap' + ), + url( + r'^m/(?P<path>.*)$', + 'django.views.static.serve', {'document_root': os.path.join(APP_PATH,'skins').replace('\\','/')}, name='askbot_media', ), - url(r'^%s(?P<path>.*)$' % _('upfiles/'), 'django.views.static.serve', + url( + r'^%s(?P<path>.*)$' % _('upfiles/'), + 'django.views.static.serve', {'document_root': os.path.join(APP_PATH,'upfiles').replace('\\','/')}, name='uploaded_file', ), - #url(r'^%s/$' % _('signin/'), 'django_authopenid.views.signin', name='signin'), url(r'^%s$' % _('about/'), app.meta.about, name='about'), url(r'^%s$' % _('faq/'), app.meta.faq, name='faq'), url(r'^%s$' % _('privacy/'), app.meta.privacy, name='privacy'), url(r'^%s$' % _('logout/'), app.meta.logout, name='logout'), - url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('comments/')), app.writers.answer_comments, name='answer_comments'), - url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('edit/')), app.writers.edit_answer, name='edit_answer'), - url(r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('revisions/')), app.readers.answer_revisions, name='answer_revisions'), - url(r'^%s$' % _('questions/'), app.readers.questions, name='questions'), - url(r'^%s%s$' % (_('questions/'), _('ask/')), app.writers.ask, name='ask'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')), app.writers.edit_question, name='edit_question'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')), app.commands.close, name='close'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')), app.commands.reopen, name='reopen'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')), app.writers.answer, name='answer'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('vote/')), app.commands.vote, name='vote'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')), app.readers.question_revisions, name='question_revisions'), - url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('comments/')), app.writers.question_comments, name='question_comments'), - url(r'^%s$' % _('command/'), app.commands.ajax_command, name='call_ajax'), - - url(r'^%s(?P<object_id>\d+)/%s(?P<comment_id>\d+)/%s$' % (_('questions/'), _('comments/'),_('delete/')), \ - app.writers.delete_comment, kwargs={'commented_object_type':'question'},\ - name='delete_question_comment'), - - url(r'^%s(?P<object_id>\d+)/%s(?P<comment_id>\d+)/%s$' % (_('answers/'), _('comments/'),_('delete/')), \ - app.writers.delete_comment, kwargs={'commented_object_type':'answer'}, \ - name='delete_answer_comment'), \ + url( + r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('comments/')), + app.writers.answer_comments, + name='answer_comments' + ), + url( + r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('edit/')), + app.writers.edit_answer, + name='edit_answer' + ), + url( + r'^%s(?P<id>\d+)/%s$' % (_('answers/'), _('revisions/')), + app.readers.answer_revisions, + name='answer_revisions' + ), + url( + r'^%s$' % _('questions/'), + app.readers.questions, + name='questions' + ), + url( + r'^%s%s$' % (_('questions/'), _('ask/')), + app.writers.ask, + name='ask' + ), + url( + r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')), + app.writers.edit_question, + name='edit_question' + ), + url( + r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')), + app.commands.close, + name='close' + ), + url( + r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('reopen/')), + app.commands.reopen, + name='reopen' + ), + url( + r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('answer/')), + app.writers.answer, + name='answer' + ), + url( + r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('vote/')), + app.commands.vote, + name='vote' + ), + url( + r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('revisions/')), + app.readers.question_revisions, + name='question_revisions' + ), + url( + r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('comments/')), + app.writers.question_comments, + name='question_comments' + ), + url( + r'^%s$' % _('command/'), + app.commands.ajax_command, + name='call_ajax' + ), + url( + r'^%s(?P<object_id>\d+)/%s(?P<comment_id>\d+)/%s$'\ + % (_('questions/'), _('comments/'),_('delete/')), + app.writers.delete_comment, + kwargs={'commented_object_type':'question'}, + name='delete_question_comment' + ), + url( + r'^%s(?P<object_id>\d+)/%s(?P<comment_id>\d+)/%s$'\ + % (_('answers/'), _('comments/'),_('delete/')), + app.writers.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.readers.question, name='question'), - url(r'^%s$' % _('tags/'), app.readers.tags, name='tags'), - - url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('interesting/')), app.commands.mark_tag, \ - kwargs={'reason':'good','action':'add'}, \ - name='mark_interesting_tag'), - - url(r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('ignored/')), app.commands.mark_tag, \ - kwargs={'reason':'bad','action':'add'}, \ - name='mark_ignored_tag'), - - url(r'^%s(?P<tag>[^/]+)/$' % _('unmark-tag/'), app.commands.mark_tag, \ - kwargs={'action':'remove'}, \ - name='mark_ignored_tag'), - - url(r'^%s$' % _('users/'),app.users.users, name='users'), - url(r'^%s(?P<id>\d+)/$' % _('moderate-user/'), app.users.moderate_user, name='moderate_user'), + url( + r'^%s(?P<id>\d+)/' % _('question/'), + app.readers.question, + name='question' + ), + url( + r'^%s$' % _('tags/'), + app.readers.tags, + name='tags' + ), + url( + r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('interesting/')), + app.commands.mark_tag, + kwargs={'reason':'good','action':'add'}, + name='mark_interesting_tag' + ), + url( + r'^%s%s(?P<tag>[^/]+)/$' % (_('mark-tag/'),_('ignored/')), + app.commands.mark_tag, + kwargs={'reason':'bad','action':'add'}, + name='mark_ignored_tag' + ), + url( + r'^%s(?P<tag>[^/]+)/$' % _('unmark-tag/'), + app.commands.mark_tag, + kwargs={'action':'remove'}, + name='mark_ignored_tag' + ), + url( + r'^%s$' % _('users/'), + app.users.users, + name='users' + ), + url( + r'^%s(?P<id>\d+)/$' % _('moderate-user/'), + app.users.moderate_user, + name='moderate_user' + ), #todo: rename as user_edit, b/c that's how template is named - url(r'^%s(?P<id>\d+)/%s$' % (_('users/'), _('edit/')), app.users.edit_user, name='edit_user'), - url(r'^%s(?P<id>\d+)/(?P<slug>.+)/$' % _('users/'), app.users.user, name='user_profile'), - url(r'^%s$' % _('badges/'),app.meta.badges, name='badges'), - url(r'^%s(?P<id>\d+)//*' % _('badges/'), app.meta.badge, name='badge'), - url(r'^%s%s$' % (_('messages/'), _('markread/')),app.commands.read_message, name='read_message'), - # (r'^admin/doc/' % _('admin/doc'), include('django.contrib.admindocs.urls')), - url(r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feeds}, name='feeds'), - url(r'^%s$' % _('upload/'), app.writers.upload, name='upload'), + url( + r'^%s(?P<id>\d+)/%s$' % (_('users/'), _('edit/')), + app.users.edit_user, + name='edit_user' + ), + url( + r'^%s(?P<id>\d+)/(?P<slug>.+)/$' % _('users/'), + app.users.user, + name='user_profile' + ), + url( + r'^%s$' % _('badges/'), + app.meta.badges, + name='badges' + ), + url( + r'^%s(?P<id>\d+)//*' % _('badges/'), + app.meta.badge, + name='badge' + ), + url( + r'^%s%s$' % (_('messages/'), _('markread/')), + app.commands.read_message, + name='read_message' + ), + #(r'^admin/doc/' % _('admin/doc'),include('django.contrib.admindocs.urls')), + url( + r'^feeds/(?P<url>.*)/$', + 'django.contrib.syndication.views.feed', + {'feed_dict': feeds}, + name='feeds' + ), + url( r'^%s$' % _('upload/'), app.writers.upload, name='upload'), url(r'^%s$' % _('search/'), app.readers.search, name='search'), url(r'^%s$' % _('feedback/'), app.meta.feedback, name='feedback'), (r'^%s' % _('account/'), include('django_authopenid.urls')), (r'^i18n/', include('django.conf.urls.i18n')), - url(r'^feeds/rss/$', RssLastestQuestionsFeed, name="latest_questions_feed"), ) - -#todo: modules -#from forum.modules import get_modules_script -#module_patterns = get_modules_script('urls') -#for pattern_file in module_patterns: -# pattern = getattr(pattern_file, 'urlpatterns', None) -# if pattern: -# urlpatterns += pattern - diff --git a/forum/user_messages/context_processors.py b/forum/user_messages/context_processors.py index 5f7b857c..dec6198d 100644 --- a/forum/user_messages/context_processors.py +++ b/forum/user_messages/context_processors.py @@ -52,4 +52,3 @@ class LazyMessages (StrAndUnicode): self._messages = get_and_delete_messages(self.request) return self._messages messages = property(_get_messages) - diff --git a/forum/utils/colors.py b/forum/utils/colors.py index 8ab092d7..8ef6f342 100644 --- a/forum/utils/colors.py +++ b/forum/utils/colors.py @@ -1,7 +1,7 @@ from forum_modules.grapefruit import Color import math -def get_counter_colors(count, max=10, empty_bg='white', empty_fg='black', +def get_counter_colors(count, counter_max=10, empty_bg='white', empty_fg='black', zero_bg='white', zero_fg='black', min_bg='white', min_fg='black', max_bg='white', max_fg='black' @@ -9,7 +9,7 @@ def get_counter_colors(count, max=10, empty_bg='white', empty_fg='black', if count == 0: return zero_fg, zero_bg - if count > max: + if count > counter_max: blend_factor = 0 else: #todo deal with negative counts properly diff --git a/forum/utils/email.py b/forum/utils/email.py index dc712572..8f719e82 100644 --- a/forum/utils/email.py +++ b/forum/utils/email.py @@ -1,4 +1,4 @@ -from django.core.mail import send_mail, EmailMultiAlternatives +from django.core.mail import EmailMultiAlternatives from django.conf import settings from django.template import loader, Context from django.utils.html import strip_tags diff --git a/forum/utils/forms.py b/forum/utils/forms.py index aeb8ec4d..05bf9f85 100644 --- a/forum/utils/forms.py +++ b/forum/utils/forms.py @@ -48,6 +48,7 @@ class UserNameField(StrippedNonEmptyCharField): self.skip_clean = skip_clean self.db_model = db_model self.db_field = db_field + self.user_instance = None error_messages={'required':_('user name is required'), 'taken':_('sorry, this name is taken, please choose another'), 'forbidden':_('sorry, this name is not allowed, please choose another'), @@ -70,10 +71,15 @@ class UserNameField(StrippedNonEmptyCharField): if self.skip_clean == True: logging.debug('username accepted with no validation') return username - if hasattr(self, 'user_instance') and isinstance(self.user_instance, User): + if self.user_instance is None: + pass + elif isinstance(self.user_instance, User): if username == self.user_instance.username: logging.debug('username valid') return username + else: + raise TypeError('user instance must be of type User') + try: username = super(UserNameField, self).clean(username) except forms.ValidationError: diff --git a/forum/utils/markup.py b/forum/utils/markup.py index 941e978b..914eb508 100644 --- a/forum/utils/markup.py +++ b/forum/utils/markup.py @@ -76,10 +76,11 @@ def mentionize_text(text, anticipated_authors): #if there is a termination character before @mention #indeed try to find a matching person text = text[pos+1:] - mentioned_author, text = extract_first_matching_mentioned_author( - text, - anticipated_authors - ) + mentioned_author, text = \ + extract_first_matching_mentioned_author( + text, + anticipated_authors + ) if mentioned_author: mentioned_authors.append(mentioned_author) output += format_mention_in_html(mentioned_author) @@ -93,7 +94,11 @@ def mentionize_text(text, anticipated_authors): else: #do this if @ is the first character text = text[1:] - mentioned_author, text = extract_first_matching_mentioned_name(text, authors) + mentioned_author, text = \ + extract_first_matching_mentioned_author( + text, + anticipated_authors + ) if mentioned_author: mentioned_authors.append(mentioned_author) output += format_mention_in_html(mentioned_author) diff --git a/forum/views/__init__.py b/forum/views/__init__.py index 291fee2a..dd390da7 100644 --- a/forum/views/__init__.py +++ b/forum/views/__init__.py @@ -1,5 +1,8 @@ -import readers -import writers -import commands -import users -import meta +""" +Forum views module +""" +from forum.views import readers +from forum.views import writers +from forum.views import commands +from forum.views import users +from forum.views import meta diff --git a/forum/views/commands.py b/forum/views/commands.py index 4746f71d..5de693b5 100644 --- a/forum/views/commands.py +++ b/forum/views/commands.py @@ -4,6 +4,7 @@ from django.conf import settings from forum.conf import settings as forum_settings from django.utils import simplejson from django.http import HttpResponse, HttpResponseRedirect +from django.http import HttpResponseForbidden from django.shortcuts import get_object_or_404, render_to_response from django.utils.translation import ugettext as _ from django.template import RequestContext @@ -276,7 +277,9 @@ def ajax_toggle_ignored_questions(request):#ajax tagging and tag-filtering syste request.user.save() @ajax_method -def ajax_command(request):#refactor? view processing ajax commands - note "vote" and view others do it too +def ajax_command(request): + """view processing ajax commands - note "vote" and view others do it too + """ if 'command' not in request.POST: return HttpResponseForbidden(mimetype="application/json") if request.POST['command'] == 'toggle-ignored-questions': diff --git a/forum/views/readers.py b/forum/views/readers.py index f75f62a4..041f44bd 100644 --- a/forum/views/readers.py +++ b/forum/views/readers.py @@ -63,13 +63,10 @@ def index(request):#generates front page - shows listing of questions sorted in """ return HttpResponseRedirect(reverse('questions')) -#todo: eliminate this from urls -def unanswered(request):#generates listing of unanswered questions - return questions(request, unanswered=True) - -def questions(request):#a view generating listing of questions, used by 'unanswered' too +def questions(request): """ List of Questions, Tagged questions, and Unanswered questions. + matching search query or user selection """ #don't allow to post to this view @@ -182,11 +179,6 @@ def search(request): #generates listing of questions matching a search query - i else: raise Http404 -#todo: eliminate this - need to go through templates to make sure -#that there are no urls pointing here -def tag(request, tag):#stub generates listing of questions tagged with a single tag - return questions(request, tagname=tag) - def tags(request):#view showing a listing of available tags - plain list stag = "" is_paginated = True diff --git a/forum/views/users.py b/forum/views/users.py index d2c003bc..cb530af1 100644 --- a/forum/views/users.py +++ b/forum/views/users.py @@ -7,19 +7,17 @@ from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect, Http404 from django.utils.translation import ugettext as _ -from django.utils.http import urlquote_plus from django.utils.html import strip_tags -from django.core.urlresolvers import reverse +from django.utils import simplejson from forum.utils.html import sanitize_html from forum import auth from forum import forms import calendar -from django.contrib.contenttypes.models import ContentType from forum import const -from django.conf import settings from forum.conf import settings as forum_settings from forum import models from forum.models import signals +import logging question_type = ContentType.objects.get_for_model(models.Question) answer_type = ContentType.objects.get_for_model(models.Answer) @@ -71,13 +69,13 @@ def users(request): base_url = reverse('users') + '?name=%s&sort=%s&' % (suser, sortby) try: - users = objects_list.page(page) + users_page = objects_list.page(page) except (EmptyPage, InvalidPage): - users = objects_list.page(objects_list.num_pages) + users_page = objects_list.page(objects_list.num_pages) return render_to_response('users.html', { 'active_tab': 'users', - 'users' : users, + 'users' : users_page, 'suser' : suser, 'keywords' : suser, 'tab_id' : sortby, @@ -85,10 +83,10 @@ def users(request): '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(), + 'has_previous': users_page.has_previous(), + 'has_next': users_page.has_next(), + 'previous': users_page.previous_page_number(), + 'next': users_page.next_page_number(), 'base_url' : base_url } @@ -607,7 +605,7 @@ def user_responses(request, user_id, user_view): if len(answers) > 0: answer_responses = [] for a in answers: - r = Response( + resp = Response( const.TYPE_RESPONSE['QUESTION_ANSWERED'], a['title'], a['question_id'], @@ -617,6 +615,7 @@ def user_responses(request, user_id, user_view): a['user_id'], a['html'] ) + answer_responses.append(resp) responses.extend(answer_responses) # question comments @@ -991,10 +990,14 @@ USER_TEMPLATE_VIEWS = ( ) ) +#todo: rename this function - variable named user is everywhere def user(request, id, slug=None): sort = request.GET.get('sort', 'stats') - user_view = dict((v.id, v) for v in USER_TEMPLATE_VIEWS).get(sort, USER_TEMPLATE_VIEWS[0]) - from forum.views import users + user_view = dict( + (v.id, v) for v in USER_TEMPLATE_VIEWS + ).get( + sort, USER_TEMPLATE_VIEWS[0] + ) func = user_view.view_func return func(request, id, user_view) diff --git a/forum/views/writers.py b/forum/views/writers.py index b6895bc7..fe2238a7 100644 --- a/forum/views/writers.py +++ b/forum/views/writers.py @@ -1,24 +1,22 @@ # encoding:utf-8 import os.path import time, datetime, random -import logging from django.core.files.storage import default_storage from django.shortcuts import render_to_response, get_object_or_404 from django.contrib.auth.decorators import login_required from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404 from django.template import RequestContext -from django.utils.html import * #todo: remove import * in favor of explicit imports from django.utils import simplejson +from django.utils.html import strip_tags from django.utils.translation import ugettext as _ from django.core.urlresolvers import reverse from django.core.exceptions import PermissionDenied +from django.conf import settings -from forum.forms import * -from forum.models import * -from forum.const import * from forum import auth -from forum.utils.forms import get_next_url from forum.views.readers import _get_tags_cache_json +from forum import forms +from forum import models # used in index page INDEX_PAGE_SIZE = 20 @@ -85,7 +83,7 @@ def ask(request):#view used to ask a new question must login/register in order for the question go be shown """ if request.method == "POST": - form = AskForm(request.POST) + form = forms.AskForm(request.POST) if form.is_valid(): added_at = datetime.datetime.now() @@ -103,7 +101,7 @@ def ask(request):#view used to ask a new question if request.user.is_authenticated(): author = request.user - question = Question.objects.create_new( + question = models.Question.objects.create_new( title = title, author = author, added_at = added_at, @@ -117,7 +115,7 @@ def ask(request):#view used to ask a new question request.session.flush() session_key = request.session.session_key summary = strip_tags(text)[:120] - question = AnonymousQuestion( + question = models.AnonymousQuestion( session_key = session_key, title = title, tagnames = tagnames, @@ -131,7 +129,7 @@ def ask(request):#view used to ask a new question return HttpResponseRedirect(reverse('user_signin_new_question')) else: #this branch is for the initial load of ask form - form = AskForm() + form = forms.AskForm() if 'title' in request.GET: #normally this title is inherited from search query #but it is possible to ask with a parameter title in the url query @@ -155,7 +153,7 @@ def ask(request):#view used to ask a new question def edit_question(request, id):#edit or retag a question """view to edit question """ - question = get_object_or_404(Question, id=id) + question = get_object_or_404(models.Question, id=id) if question.deleted and not auth.can_view_deleted_post(request.user, question): raise Http404 if auth.can_edit_post(request.user, question): @@ -170,7 +168,7 @@ def _retag_question(request, question):#non-url subview of edit question - just view "edit_question" """ if request.method == 'POST': - form = RetagQuestionForm(question, request.POST) + form = forms.RetagQuestionForm(question, request.POST) if form.is_valid(): if form.has_changed(): question.retag( @@ -180,7 +178,7 @@ def _retag_question(request, question):#non-url subview of edit question - just ) return HttpResponseRedirect(question.get_absolute_url()) else: - form = RetagQuestionForm(question) + form = forms.RetagQuestionForm(question) return render_to_response('question_retag.html', { 'active_tab': 'questions', 'question': question, @@ -194,17 +192,17 @@ def _edit_question(request, question):#non-url subview of edit_question - just e if request.method == 'POST': if 'select_revision' in request.POST:#revert-type edit # user has changed revistion number - revision_form = RevisionForm(question, latest_revision, request.POST) + revision_form = forms.RevisionForm(question, latest_revision, request.POST) if revision_form.is_valid(): # Replace with those from the selected revision - form = EditQuestionForm(question, - QuestionRevision.objects.get(question=question, + form = forms.EditQuestionForm(question, + models.QuestionRevision.objects.get(question=question, revision=revision_form.cleaned_data['revision'])) else: - form = EditQuestionForm(question, latest_revision, request.POST) + form = forms.EditQuestionForm(question, latest_revision, request.POST) else:#new content edit # Always check modifications against the latest revision - form = EditQuestionForm(question, latest_revision, request.POST) + form = forms.EditQuestionForm(question, latest_revision, request.POST) if form.is_valid(): if form.has_changed(): edited_at = datetime.datetime.now() @@ -222,8 +220,8 @@ def _edit_question(request, question):#non-url subview of edit_question - just e return HttpResponseRedirect(question.get_absolute_url()) else: - revision_form = RevisionForm(question, latest_revision) - form = EditQuestionForm(question, latest_revision) + revision_form = forms.RevisionForm(question, latest_revision) + form = forms.EditQuestionForm(question, latest_revision) return render_to_response('question_edit.html', { 'active_tab': 'questions', 'question': question, @@ -234,7 +232,7 @@ def _edit_question(request, question):#non-url subview of edit_question - just e @login_required def edit_answer(request, id): - answer = get_object_or_404(Answer, id=id) + answer = get_object_or_404(models.Answer, id=id) if answer.deleted and not auth.can_view_deleted_post(request.user, answer): raise Http404 elif not auth.can_edit_post(request.user, answer): @@ -244,16 +242,16 @@ def edit_answer(request, id): if request.method == "POST": if 'select_revision' in request.POST: # user has changed revistion number - revision_form = RevisionForm(answer, latest_revision, request.POST) + revision_form = forms.RevisionForm(answer, latest_revision, request.POST) if revision_form.is_valid(): # Replace with those from the selected revision - form = EditAnswerForm(answer, - AnswerRevision.objects.get(answer=answer, + form = forms.EditAnswerForm(answer, + models.AnswerRevision.objects.get(answer=answer, revision=revision_form.cleaned_data['revision'])) else: - form = EditAnswerForm(answer, latest_revision, request.POST) + form = forms.EditAnswerForm(answer, latest_revision, request.POST) else: - form = EditAnswerForm(answer, latest_revision, request.POST) + form = forms.EditAnswerForm(answer, latest_revision, request.POST) if form.is_valid(): if form.has_changed(): edited_at = datetime.datetime.now() @@ -266,8 +264,8 @@ def edit_answer(request, id): ) return HttpResponseRedirect(answer.get_absolute_url()) else: - revision_form = RevisionForm(answer, latest_revision) - form = EditAnswerForm(answer, latest_revision) + revision_form = forms.RevisionForm(answer, latest_revision) + form = forms.EditAnswerForm(answer, latest_revision) return render_to_response('answer_edit.html', { 'active_tab': 'questions', 'answer': answer, @@ -275,17 +273,18 @@ def edit_answer(request, id): 'form': form, }, context_instance=RequestContext(request)) +#todo: rename this function to post_new_answer def answer(request, id):#process a new answer - question = get_object_or_404(Question, id=id) + question = get_object_or_404(models.Question, id=id) if request.method == "POST": - form = AnswerForm(question, request.user, request.POST) + form = forms.AnswerForm(question, request.user, request.POST) if form.is_valid(): wiki = form.cleaned_data['wiki'] text = form.cleaned_data['text'] update_time = datetime.datetime.now() if request.user.is_authenticated(): - Answer.objects.create_new( + models.Answer.objects.create_new( question=question, author=request.user, added_at=update_time, @@ -295,7 +294,7 @@ def answer(request, id):#process a new answer ) else: request.session.flush() - anon = AnonymousAnswer( + anon = models.AnonymousAnswer( question=question, wiki=wiki, text=text, @@ -308,7 +307,9 @@ def answer(request, id):#process a new answer return HttpResponseRedirect(question.get_absolute_url()) -def __generate_comments_json(obj, type, user):#non-view generates json data for the post comments +def __generate_comments_json(obj, comment_type, user): + """non-view generates json data for the post comments + """ comments = obj.comments.all().order_by('id') # {"Id":6,"PostId":38589,"CreationDate":"an hour ago","Text":"hello there!","UserDisplayName":"Jarrod Dixon","UserUrl":"/users/3/jarrod-dixon","DeleteUrl":null} json_comments = [] @@ -319,7 +320,8 @@ def __generate_comments_json(obj, type, user):#non-view generates json data for if user != None and auth.can_delete_comment(user, comment): #/posts/392845/comments/219852/delete #todo translate this url - delete_url = reverse('index') + type + "s/%s/comments/%s/delete/" % (obj.id, comment.id) + delete_url = reverse('index') + comment_type + \ + "s/%s/comments/%s/delete/" % (obj.id, comment.id) json_comments.append({"id" : comment.id, "object_id" : obj.id, "comment_age" : diff_date(comment.added_at), @@ -333,42 +335,39 @@ def __generate_comments_json(obj, type, user):#non-view generates json data for return HttpResponse(data, mimetype="application/json") def question_comments(request, id):#ajax handler for loading comments to question - question = get_object_or_404(Question, id=id) - user = request.user + question = get_object_or_404(models.Question, id=id) return __comments(request, question, 'question') def answer_comments(request, id):#ajax handler for loading comments on answer - answer = get_object_or_404(Answer, id=id) - user = request.user + answer = get_object_or_404(models.Answer, id=id) return __comments(request, answer, 'answer') -def __comments(request, obj, type):#non-view generic ajax handler to load comments to an object +def __comments(request, obj, comment_type):#non-view generic ajax handler to load comments to an object # only support get post comments by ajax now user = request.user if request.is_ajax(): if request.method == "GET": - response = __generate_comments_json(obj, type, user) + response = __generate_comments_json(obj, comment_type, user) elif request.method == "POST": - if auth.can_add_comments(user,obj): + if auth.can_add_comments(user, obj): obj.add_comment( comment = request.POST.get('comment'), user = request.user, ) - response = __generate_comments_json(obj, type, user) + response = __generate_comments_json(obj, comment_type, user) else: response = HttpResponseForbidden(mimetype="application/json") return response def delete_comment(request, object_id='', comment_id='', commented_object_type=None):#ajax handler to delete comment - response = None commented_object = None if commented_object_type == 'question': - commented_object = Question + commented_object = models.Question elif commented_object_type == 'answer': - commented_object = Answer + commented_object = models.Answer if request.is_ajax(): - comment = get_object_or_404(Comment, id=comment_id) + comment = get_object_or_404(models.Comment, id=comment_id) if auth.can_delete_comment(request.user, comment): obj = get_object_or_404(commented_object, id=object_id) obj.comments.remove(comment) diff --git a/settings.py b/settings.py index 98555997..1f78d0e6 100644 --- a/settings.py +++ b/settings.py @@ -13,7 +13,6 @@ TEMPLATE_LOADERS = ( 'django.template.loaders.app_directories.load_template_source', #below is forum stuff for this tuple - 'forum.modules.module_templates_loader',#todo: remove this 'forum.skins.load_template_source',#forum stuff # 'django.template.loaders.eggs.load_template_source', ) @@ -59,10 +58,6 @@ FILE_UPLOAD_TEMP_DIR = os.path.join(os.path.dirname(__file__), 'tmp').replace('\ FILE_UPLOAD_HANDLERS = ("django.core.files.uploadhandler.MemoryFileUploadHandler", "django.core.files.uploadhandler.TemporaryFileUploadHandler",) DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' -# for user upload -ALLOW_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff') -# unit byte -ALLOW_MAX_FILE_SIZE = 1024 * 1024 # User settings from settings_local import * @@ -80,7 +75,6 @@ INSTALLED_APPS = ( 'debug_toolbar', 'forum', 'django_authopenid', - 'debug_toolbar' , #'forum.importers.stackexchange', #se loader 'south', 'livesettings', diff --git a/settings_local.py.dist b/settings_local.py.dist index 6294a28c..fc6815da 100755 --- a/settings_local.py.dist +++ b/settings_local.py.dist @@ -37,6 +37,11 @@ CACHE_BACKEND = 'dummy://' #If you use memcache you may want to uncomment the following line to enable memcached based sessions #SESSION_ENGINE = 'django.contrib.sessions.backends.cache_db' +# for user upload + +ALLOW_FILE_TYPES = ('.jpg', '.jpeg', '.gif', '.bmp', '.png', '.tiff') +# unit byte +ALLOW_MAX_FILE_SIZE = 1024 * 1024 #email server settings SERVER_EMAIL = '' |