diff options
54 files changed, 623 insertions, 181 deletions
diff --git a/askbot/deployment/path_utils.py b/askbot/deployment/path_utils.py index ef260f86..caefa2a9 100644 --- a/askbot/deployment/path_utils.py +++ b/askbot/deployment/path_utils.py @@ -154,7 +154,7 @@ def deploy_into(directory, new_project = False, verbosity = 1, context = None): """ assert(isinstance(new_project, bool)) if new_project: - copy_files = ('__init__.py', 'manage.py', 'urls.py') + copy_files = ('__init__.py', 'manage.py', 'urls.py', 'django.wsgi') blank_files = ('__init__.py', 'manage.py') if verbosity >= 1: print 'Copying files: ' diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst index e2056b61..54baa18f 100644 --- a/askbot/doc/source/changelog.rst +++ b/askbot/doc/source/changelog.rst @@ -10,6 +10,14 @@ Development version (not released yet) * Fixed the url translation bug (Evgeny) * Added left sidebar option (Evgeny) * Added "help" page and links to in the header and the footer (Evgeny) +* Removed url parameters and the hash fragment from uploaded files - + amazon S3 for some reason adds weird expiration parameters (Evgeny) +* Reduced memory usage in data migrations (Evgeny) +* Added progress bars to slow data migrations (Evgeny) +* Added a management command to build_thread_summary_cache (Evgeny) +* Added a management delete_contextless_badge_award_activities (Evgeny) +* Fixed a file upload issue in FF and IE found by jerry_gzy (Evgeny) +* Added test on maximum length of title working for utf-8 text (Evgeny) 0.7.39 (Jan 11, 2012) --------------------- diff --git a/askbot/doc/source/management-commands.rst b/askbot/doc/source/management-commands.rst index 1e3a7ac0..b857d6f0 100644 --- a/askbot/doc/source/management-commands.rst +++ b/askbot/doc/source/management-commands.rst @@ -78,6 +78,12 @@ The bulk of the management commands fall into this group and will probably be th | | This data is used to display preferentially real faces | | | on the main page. | +---------------------------------+-------------------------------------------------------------+ +| `build_thread_summary_cache` | Rebuilds cache for the question summary snippet. | ++---------------------------------+-------------------------------------------------------------+ +| `delete_contextless_...` | `delete_contextless_badge_award_activities` | +| | Deletes Activity objects of type badge award where the | +| | related context object is lost. | ++---------------------------------+-------------------------------------------------------------+ .. _email-related-commands: diff --git a/askbot/forms.py b/askbot/forms.py index 08645fcd..fcf43814 100644 --- a/askbot/forms.py +++ b/askbot/forms.py @@ -113,6 +113,22 @@ class TitleField(forms.CharField): askbot_settings.MIN_TITLE_LENGTH ) % askbot_settings.MIN_TITLE_LENGTH raise forms.ValidationError(msg) + encoded_value = value.encode('utf-8') + if len(value) == len(encoded_value): + if len(value) > self.max_length: + raise forms.ValidationError( + _( + 'The title is too long, maximum allowed size is ' + '%d characters' + ) % self.max_length + ) + elif encoded_value > self.max_length: + raise forms.ValidationError( + _( + 'The title is too long, maximum allowed size is ' + '%d bytes' + ) % self.max_length + ) return value.strip() # TODO: test me diff --git a/askbot/management/commands/build_thread_summary_cache.py b/askbot/management/commands/build_thread_summary_cache.py new file mode 100644 index 00000000..854843fe --- /dev/null +++ b/askbot/management/commands/build_thread_summary_cache.py @@ -0,0 +1,10 @@ +from django.core.management.base import NoArgsCommand +from askbot.models import Thread +from askbot.utils.console import ProgressBar + +class Command(NoArgsCommand): + def handle_noargs(self, **options): + message = "Rebuilding thread summary cache" + count = Thread.objects.count() + for thread in ProgressBar(Thread.objects.iterator(), count, message): + thread.update_summary_html() diff --git a/askbot/management/commands/delete_contextless_badge_award_activities.py b/askbot/management/commands/delete_contextless_badge_award_activities.py new file mode 100644 index 00000000..8116d335 --- /dev/null +++ b/askbot/management/commands/delete_contextless_badge_award_activities.py @@ -0,0 +1,20 @@ +from django.core.management.base import NoArgsCommand +from askbot.utils.console import ProgressBar +from askbot.models import Activity +from askbot import const + +class Command(NoArgsCommand): + def handle_noargs(self, **options): + act_type = const.TYPE_ACTIVITY_PRIZE + acts = Activity.objects.filter(activity_type = act_type) + deleted_count = 0 + message = "Searching for context-less award activity objects:" + for act in ProgressBar(acts.iterator(), acts.count(), message): + if act.content_object == None: + act.delete() + deleted_count += 1 + if deleted_count: + print "%d activity objects deleted" % deleted_count + else: + print "None found" + diff --git a/askbot/migrations/0050_move_qa_revisions_to_postrevision.py b/askbot/migrations/0050_move_qa_revisions_to_postrevision.py index 36c46551..e4d0ea3b 100644 --- a/askbot/migrations/0050_move_qa_revisions_to_postrevision.py +++ b/askbot/migrations/0050_move_qa_revisions_to_postrevision.py @@ -42,10 +42,10 @@ class Migration(DataMigration): def forwards(self, orm): # Process revisions - for qr in orm.QuestionRevision.objects.all(): + for qr in orm.QuestionRevision.objects.iterator(): self.copy_revision(orm=orm, source_revision=qr) - for ar in orm.AnswerRevision.objects.all(): + for ar in orm.AnswerRevision.objects.iterator(): self.copy_revision(orm=orm, source_revision=ar) diff --git a/askbot/migrations/0053_create_threads_for_questions.py b/askbot/migrations/0053_create_threads_for_questions.py index e2fe0d40..8c734571 100644 --- a/askbot/migrations/0053_create_threads_for_questions.py +++ b/askbot/migrations/0053_create_threads_for_questions.py @@ -3,12 +3,15 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): "Write your forwards methods here." - for question in orm.Question.objects.all(): + print "Converting question to threads:" + num_questions = orm.Question.objects.count() + for question in ProgressBar(orm.Question.objects.iterator(), num_questions): thread = orm.Thread.objects.create(favourite_count=question.favourite_count) question.thread = thread question.save() diff --git a/askbot/migrations/0057_transplant_answer_count_data.py b/askbot/migrations/0057_transplant_answer_count_data.py index bd831f66..f83847cd 100644 --- a/askbot/migrations/0057_transplant_answer_count_data.py +++ b/askbot/migrations/0057_transplant_answer_count_data.py @@ -3,11 +3,14 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): - for question in orm.Question.objects.all(): + message = "Adding answer counts to threads" + num_questions = orm.Question.objects.count() + for question in ProgressBar(orm.Question.objects.iterator(), num_questions, message): thread = question.thread thread.answer_count = question.answer_count thread.save() diff --git a/askbot/migrations/0060_view_count_transplant.py b/askbot/migrations/0060_view_count_transplant.py index 07fb13d4..e9538655 100644 --- a/askbot/migrations/0060_view_count_transplant.py +++ b/askbot/migrations/0060_view_count_transplant.py @@ -3,11 +3,14 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): - for question in orm.Question.objects.all(): + message = "Adding view counts to threads" + num_questions = orm.Question.objects.count() + for question in ProgressBar(orm.Question.objects.iterator(), num_questions, message): thread = question.thread thread.view_count = question.view_count thread.save() diff --git a/askbot/migrations/0063_transplant_question_closed_datas.py b/askbot/migrations/0063_transplant_question_closed_datas.py index b2302d33..15626e48 100644 --- a/askbot/migrations/0063_transplant_question_closed_datas.py +++ b/askbot/migrations/0063_transplant_question_closed_datas.py @@ -3,11 +3,14 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): - for question in orm.Question.objects.all(): + message = "Marking closed threads" + num_questions = orm.Question.objects.count() + for question in ProgressBar(orm.Question.objects.iterator(), num_questions, message): thread = question.thread thread.closed = question.closed diff --git a/askbot/migrations/0066_transplant_accepted_answer_data.py b/askbot/migrations/0066_transplant_accepted_answer_data.py index 6f5a81a0..f9c28b94 100644 --- a/askbot/migrations/0066_transplant_accepted_answer_data.py +++ b/askbot/migrations/0066_transplant_accepted_answer_data.py @@ -5,11 +5,14 @@ from south.v2 import DataMigration from django.db import models from askbot.migrations import TERM_YELLOW, TERM_RESET +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): - for question in orm.Question.objects.all(): + message = "Adding accepted answers to threads" + num_questions = orm.Question.objects.count() + for question in ProgressBar(orm.Question.objects.iterator(), num_questions, message): thread = question.thread if question.answer_accepted: @@ -27,7 +30,8 @@ class Migration(DataMigration): thread.save() # Verify data integrity - for question in orm.Question.objects.all(): + message = "Checking correctness of accepted answer data" + for question in ProgressBar(orm.Question.objects.iterator(), num_questions, message): accepted_answers = question.answers.filter(accepted=True) num_accepted_answers = len(accepted_answers) diff --git a/askbot/migrations/0069_transplant_last_activity_data.py b/askbot/migrations/0069_transplant_last_activity_data.py index 5d612254..bcd83ec9 100644 --- a/askbot/migrations/0069_transplant_last_activity_data.py +++ b/askbot/migrations/0069_transplant_last_activity_data.py @@ -3,11 +3,14 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): - for question in orm.Question.objects.all(): + message = "Adding last activity data to threads" + num_questions = orm.Question.objects.count() + for question in ProgressBar(orm.Question.objects.iterator(), num_questions, message): thread = question.thread thread.last_activity_at = question.last_activity_at thread.last_activity_by = question.last_activity_by diff --git a/askbot/migrations/0072_transplant_tagnames_data.py b/askbot/migrations/0072_transplant_tagnames_data.py index 3d3fc92a..caa5e6a6 100644 --- a/askbot/migrations/0072_transplant_tagnames_data.py +++ b/askbot/migrations/0072_transplant_tagnames_data.py @@ -3,11 +3,14 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): - for question in orm.Question.objects.all(): + message = "Adding denormalized tags field to threads" + num_questions = orm.Question.objects.count() + for question in ProgressBar(orm.Question.objects.iterator(), num_questions, message): thread = question.thread thread.tagnames = question.tagnames thread.save() diff --git a/askbot/migrations/0075_transplant_followed_by_data.py b/askbot/migrations/0075_transplant_followed_by_data.py index 822300ff..06afd155 100644 --- a/askbot/migrations/0075_transplant_followed_by_data.py +++ b/askbot/migrations/0075_transplant_followed_by_data.py @@ -3,13 +3,16 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): - for question in orm.Question.objects.all(): + message = "Adding followers to threads" + num_questions = orm.Question.objects.count() + for question in ProgressBar(orm.Question.objects.iterator(), num_questions, message): question.thread.followed_by.clear() # just in case someone reversed this migration - question.thread.followed_by.add(*list(question.followed_by.all())) + question.thread.followed_by.add(*list(question.followed_by.iterator())) if question.followed_by.count() != question.thread.followed_by.count(): raise ValueError("There are Thread instances for which data doesn't match Question!") diff --git a/askbot/migrations/0079_transplant_favquestions_data.py b/askbot/migrations/0079_transplant_favquestions_data.py index 351dc7f8..67decb07 100644 --- a/askbot/migrations/0079_transplant_favquestions_data.py +++ b/askbot/migrations/0079_transplant_favquestions_data.py @@ -3,11 +3,14 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): - for fav in orm.FavoriteQuestion.objects.all(): + message = "Connecting favorite questions to threads" + num_favs = orm.FavoriteQuestion.objects.count() + for fav in ProgressBar(orm.FavoriteQuestion.objects.iterator(), num_favs, message): fav.thread = fav.question.thread fav.save() diff --git a/askbot/migrations/0082_transplant_title_data.py b/askbot/migrations/0082_transplant_title_data.py index 6f4a07d6..ccdf1e16 100644 --- a/askbot/migrations/0082_transplant_title_data.py +++ b/askbot/migrations/0082_transplant_title_data.py @@ -3,11 +3,14 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): - for question in orm.Question.objects.all(): + message = "Adding titles to threads" + num_questions = orm.Question.objects.count() + for question in ProgressBar(orm.Question.objects.iterator(), num_questions, message): question.thread.title = question.title question.thread.save() diff --git a/askbot/migrations/0086_transplant_question_tags_data.py b/askbot/migrations/0086_transplant_question_tags_data.py index a7508229..c2d9fa76 100644 --- a/askbot/migrations/0086_transplant_question_tags_data.py +++ b/askbot/migrations/0086_transplant_question_tags_data.py @@ -3,12 +3,15 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): - for question in orm.Question.objects.all(): - question.thread.tags.add(*list(question.tags.all())) + message = "Linking tag objects with threads" + num_questions = orm.Question.objects.count() + for question in ProgressBar(orm.Question.objects.iterator(), num_questions, message): + question.thread.tags.add(*list(question.tags.iterator())) if orm.Question.objects.annotate(tag_num=models.Count('tags'), thread_tag_num=models.Count('thread__tags')).exclude(tag_num=models.F('thread_tag_num')).exists(): raise ValueError("There are Thread instances for which data doesn't match Question!") diff --git a/askbot/migrations/0090_postize_q_a_c.py b/askbot/migrations/0090_postize_q_a_c.py index f85cbf09..7c85ed21 100644 --- a/askbot/migrations/0090_postize_q_a_c.py +++ b/askbot/migrations/0090_postize_q_a_c.py @@ -5,6 +5,7 @@ from south.db import db from south.v2 import DataMigration from django.db import models from django.conf import settings +from askbot.utils.console import ProgressBar from askbot.migrations import TERM_RED_BOLD, TERM_GREEN, TERM_RESET @@ -26,7 +27,9 @@ class Migration(DataMigration): if 'test' not in sys.argv: # Don't confuse users print TERM_GREEN, '[DEBUG] Initial Post.id ==', post_id + 1, TERM_RESET - for q in orm.Question.objects.all(): + message = "Converting Questions -> Posts" + num_questions = orm.Question.objects.count() + for q in ProgressBar(orm.Question.objects.iterator(), num_questions, message): post_id += 1 orm.Post.objects.create( id=post_id, @@ -67,7 +70,9 @@ class Migration(DataMigration): is_anonymous=q.is_anonymous, ) - for ans in orm.Answer.objects.all(): + message = "Answers -> Posts" + num_answers = orm.Answer.objects.count() + for ans in ProgressBar(orm.Answer.objects.iterator(), num_answers, message): post_id += 1 orm.Post.objects.create( id=post_id, @@ -108,7 +113,9 @@ class Migration(DataMigration): is_anonymous=ans.is_anonymous, ) - for cm in orm.Comment.objects.all(): + message = "Comments -> Posts" + num_comments = orm.Comment.objects.count() + for cm in ProgressBar(orm.Comment.objects.iterator(), num_comments, message): # Workaround for a strange issue with: http://south.aeracode.org/docs/generics.html # No need to investigate that as this is as simple as the "proper" way if (cm.content_type.app_label, cm.content_type.model) == ('askbot', 'question'): diff --git a/askbot/migrations/0092_postize_vote_and_activity.py b/askbot/migrations/0092_postize_vote_and_activity.py index 556ef18a..9c77597d 100644 --- a/askbot/migrations/0092_postize_vote_and_activity.py +++ b/askbot/migrations/0092_postize_vote_and_activity.py @@ -6,13 +6,15 @@ from south.v2 import DataMigration from django.db import models from askbot.migrations import TERM_RED_BOLD, TERM_GREEN, TERM_RESET +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): - # TODO: Speed this up by prefetching all votes ? - for v in orm.Vote.objects.all(): + message = "Connecting votes to posts" + num_votes = orm.Vote.objects.count() + for v in ProgressBar(orm.Vote.objects.iterator(), num_votes, message): if (v.content_type.app_label, v.content_type.model) == ('askbot', 'question'): v.voted_post = orm.Post.objects.get(self_question__id=v.object_id) elif (v.content_type.app_label, v.content_type.model) == ('askbot', 'answer'): @@ -31,7 +33,9 @@ class Migration(DataMigration): abandoned_activities = [] - for a in orm.Activity.objects.all(): + message = "Connecting activity objects to posts" + num_activities = orm.Activity.objects.count() + for a in ProgressBar(orm.Activity.objects.iterator(), num_activities, message): # test if content_object for this activity exists - there might be a bunch of "abandoned" activities # # NOTE that if activity.content_object is gone then we cannot reliably recover it from activity.question diff --git a/askbot/migrations/0095_postize_award_and_repute.py b/askbot/migrations/0095_postize_award_and_repute.py index 0071199c..ea7e7064 100644 --- a/askbot/migrations/0095_postize_award_and_repute.py +++ b/askbot/migrations/0095_postize_award_and_repute.py @@ -4,6 +4,7 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from askbot.utils.console import ProgressBar class Migration(DataMigration): @@ -11,16 +12,23 @@ class Migration(DataMigration): # ContentType for Post model should be created no later than in migration 0092 ct_post = orm['contenttypes.ContentType'].objects.get(app_label='askbot', model='post') - for aw in orm.Award.objects.all(): + message = "Connecting award objects to posts" + num_awards = orm.Award.objects.count() + for aw in ProgressBar(orm.Award.objects.iterator(), num_awards, message): ct = aw.content_type if ct.app_label == 'askbot' and ct.model in ('question', 'answer', 'comment'): aw.content_type = ct_post - aw.object_id = orm.Post.objects.get(**{'self_%s__id' % str(ct.model): aw.object_id}).id + try: + aw.object_id = orm.Post.objects.get(**{'self_%s__id' % str(ct.model): aw.object_id}).id + except orm.Post.DoesNotExist: + continue aw.save() ### - for rp in orm.Repute.objects.all(): + message = "Connecting repute objects to posts" + num_reputes = orm.Repute.objects.count() + for rp in ProgressBar(orm.Repute.objects.iterator(), num_reputes, message): if rp.question: rp.question_post = orm.Post.objects.get(self_question__id=rp.question.id) rp.save() diff --git a/askbot/migrations/0098_postize_thread_anonanswer_questionview_postrevision.py b/askbot/migrations/0098_postize_thread_anonanswer_questionview_postrevision.py index e253613e..08e62dbb 100644 --- a/askbot/migrations/0098_postize_thread_anonanswer_questionview_postrevision.py +++ b/askbot/migrations/0098_postize_thread_anonanswer_questionview_postrevision.py @@ -3,26 +3,35 @@ import datetime from south.db import db from south.v2 import DataMigration from django.db import models +from askbot.utils.console import ProgressBar class Migration(DataMigration): def forwards(self, orm): # TODO: Speed this migration up by prefetching data ? - for thread in orm.Thread.objects.all(): + message = "Marking accepted answer in threads" + num_threads = orm.Thread.objects.count() + for thread in ProgressBar(orm.Thread.objects.iterator(), num_threads, message): if thread.accepted_answer: thread.accepted_answer_post = orm.Post.objects.get(self_answer__id=thread.accepted_answer.id) thread.save() - for qv in orm.QuestionView.objects.all(): + message = "Connecting question view objects to posts" + num_question_views = orm.QuestionView.objects.count() + for qv in ProgressBar(orm.QuestionView.objects.iterator(), num_question_views, message): qv.question_post = orm.Post.objects.get(self_question__id=qv.question.id) qv.save() - for aa in orm.AnonymousAnswer.objects.all(): + message = "Connecting anonymous answers to posts" + num_anon_answers = orm.AnonymousAnswer.objects.count() + for aa in ProgressBar(orm.AnonymousAnswer.objects.iterator(), num_anon_answers, message): aa.question_post = orm.Post.objects.get(self_question__id=aa.question.id) aa.save() - for rev in orm.PostRevision.objects.all(): + message = "Connecting post revisions to posts" + num_post_revs = orm.PostRevision.objects.count() + for rev in ProgressBar(orm.PostRevision.objects.iterator(), num_post_revs, message): if rev.question: assert not rev.answer rev.post = orm.Post.objects.get(self_question__id=rev.question.id) diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index d98d4246..ee2a6f4a 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -36,6 +36,7 @@ from askbot import auth from askbot.utils.decorators import auto_now_timestamp from askbot.utils.slug import slugify from askbot.utils.diff import textDiff as htmldiff +from askbot.utils.url_utils import strip_path from askbot.utils import mail def get_model(model_name): @@ -488,6 +489,18 @@ def user_assert_can_edit_comment(self, comment = None): raise django_exceptions.PermissionDenied(error_message) +def user_can_post_comment(self, parent_post = None): + """a simplified method to test ability to comment + """ + if self.reputation >= askbot_settings.MIN_REP_TO_LEAVE_COMMENTS: + return True + if self == parent_post.author: + return True + if self.is_administrator_or_moderator(): + return True + return False + + def user_assert_can_post_comment(self, parent_post = None): """raises exceptions.PermissionDenied if user cannot post comment @@ -2134,6 +2147,7 @@ User.add_to_class('is_following_question', user_is_following_question) User.add_to_class('mark_tags', user_mark_tags) User.add_to_class('update_response_counts', user_update_response_counts) User.add_to_class('can_have_strong_url', user_can_have_strong_url) +User.add_to_class('can_post_comment', user_can_post_comment) User.add_to_class('is_administrator', user_is_administrator) User.add_to_class('is_administrator_or_moderator', user_is_administrator_or_moderator) User.add_to_class('set_admin_status', user_set_admin_status) @@ -2279,7 +2293,7 @@ def format_instant_notification_email( 'receiving_user_name': to_user.username, 'content_preview': content_preview,#post.get_snippet() 'update_type': update_type, - 'post_url': site_url + post.get_absolute_url(), + 'post_url': strip_path(site_url) + post.get_absolute_url(), 'origin_post_title': origin_post.thread.title, 'user_subscriptions_url': user_subscriptions_url, } diff --git a/askbot/models/post.py b/askbot/models/post.py index 5cb9708f..1543a438 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -459,7 +459,7 @@ class Post(models.Model): if self.is_answer(): if not question_post: question_post = self.thread._question_post() - return u'%(base)s%(slug)s?answer=%(id)d#answer-container-%(id)d' % { + return u'%(base)s%(slug)s?answer=%(id)d#post-id-%(id)d' % { 'base': urlresolvers.reverse('question', args=[question_post.id]), 'slug': django_urlquote(slugify(self.thread.title)), 'id': self.id diff --git a/askbot/models/question.py b/askbot/models/question.py index 6c2fa383..6daa3057 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -248,7 +248,7 @@ class ThreadManager(models.Manager): #print qs.query - return qs, meta_data + return qs.distinct(), meta_data def precache_view_data_hack(self, threads): # TODO: Re-enable this when we have a good test cases to verify that it works properly. diff --git a/askbot/skins/common/media/jquery-openid/jquery.openid.js b/askbot/skins/common/media/jquery-openid/jquery.openid.js index 29b31b34..249413b9 100644 --- a/askbot/skins/common/media/jquery-openid/jquery.openid.js +++ b/askbot/skins/common/media/jquery-openid/jquery.openid.js @@ -193,7 +193,6 @@ $.fn.authenticator = function() { password_input_fields.hide(); } reset_password_input_fields(); - $('.error').remove(); if (userIsAuthenticated === false){ email_input_fields.hide(); account_recovery_heading.hide(); @@ -210,13 +209,18 @@ $.fn.authenticator = function() { } }; + var reset_form_and_errors = function(){ + reset_form(); + $('.error').remove(); + } + var set_provider_name = function(element){ var provider_name = element.attr('name'); provider_name_input.val(provider_name); }; var show_openid_input_fields = function(provider_name){ - reset_form(); + reset_form_and_errors(); var token_name = extra_token_name[provider_name] if (userIsAuthenticated){ $('#openid-heading').html( @@ -290,7 +294,7 @@ $.fn.authenticator = function() { var start_password_login_or_change = function(){ //called upon clicking on one of the password login buttons - reset_form(); + reset_form_and_errors(); set_provider_name($(this)); var provider_name = $(this).attr('name'); return setup_password_login_or_change(provider_name); @@ -370,7 +374,7 @@ $.fn.authenticator = function() { }; var start_account_recovery = function(){ - reset_form(); + reset_form_and_errors(); account_recovery_hint.hide(); account_recovery_heading.css('margin-bottom', '0px'); account_recovery_heading.html(account_recovery_prompt_text).show(); diff --git a/askbot/skins/common/media/js/editor.js b/askbot/skins/common/media/js/editor.js index e580f9f6..2d1f5670 100644 --- a/askbot/skins/common/media/js/editor.js +++ b/askbot/skins/common/media/js/editor.js @@ -42,11 +42,11 @@ function ajaxFileUpload(imageUrl, startUploadHandler) url: askbot['urls']['upload'], secureuri:false, fileElementId:'file-upload', - dataType: 'json', + dataType: 'xml', success: function (data, status) { - var fileURL = data['file_url']; - var error = data['error']; + var fileURL = $(data).find('file_url').text(); + var error = $(data).find('error').text(); if(error != ''){ alert(error); if (startUploadHandler){ @@ -57,6 +57,7 @@ function ajaxFileUpload(imageUrl, startUploadHandler) }else{ imageUrl.attr('value', fileURL); } + }, error: function (data, status, e) { @@ -71,4 +72,4 @@ function ajaxFileUpload(imageUrl, startUploadHandler) ) return false; -} +}; diff --git a/askbot/skins/common/media/js/post.js b/askbot/skins/common/media/js/post.js index 2f2fbd75..dc3fbfd7 100644 --- a/askbot/skins/common/media/js/post.js +++ b/askbot/skins/common/media/js/post.js @@ -291,7 +291,7 @@ var Vote = function(){ var postId; var questionAuthorId; var currentUserId; - var answerContainerIdPrefix = 'answer-container-'; + var answerContainerIdPrefix = 'post-id-'; var voteContainerId = 'vote-buttons'; var imgIdPrefixAccept = 'answer-img-accept-'; var classPrefixFollow= 'button follow'; @@ -349,8 +349,8 @@ var Vote = function(){ offensiveAnswer:8, removeOffensiveAnswer:8.5, removeAllOffensiveAnswer:8.6, - removeQuestion: 9, - removeAnswer:10, + removeQuestion: 9,//deprecate + removeAnswer:10,//deprecate questionSubscribeUpdates:11, questionUnsubscribeUpdates:12 }; @@ -520,9 +520,9 @@ var Vote = function(){ Vote.remove_all_offensive(this, VoteType.removeAllOffensiveAnswer); }); - getremoveQuestionLink().unbind('click').click(function(event){ - Vote.remove(this, VoteType.removeQuestion); - }); + //getremoveQuestionLink().unbind('click').click(function(event){ + // Vote.remove(this, VoteType.removeQuestion); + //}); getquestionSubscribeUpdatesCheckbox().unbind('click').click(function(event){ //despeluchar esto @@ -927,7 +927,7 @@ var Vote = function(){ var do_proceed = false; if (postType == 'answer'){ - postNode = $('#answer-container-' + postId); + postNode = $('#post-id-' + postId); } else if (postType == 'question'){ postNode = $('#question-table'); @@ -1126,6 +1126,73 @@ var questionRetagger = function(){ }; }(); +var DeletePostLink = function(){ + SimpleControl.call(this); + this._post_id = null; +}; +inherits(DeletePostLink, SimpleControl); + +DeletePostLink.prototype.setPostId = function(id){ + this._post_id = id; +}; + +DeletePostLink.prototype.getPostId = function(){ + return this._post_id; +}; + +DeletePostLink.prototype.getPostElement = function(){ + return $('#post-id-' + this.getPostId()); +}; + +DeletePostLink.prototype.isPostDeleted = function(){ + return this._post_deleted; +}; + +DeletePostLink.prototype.setPostDeleted = function(is_deleted){ + var post = this.getPostElement(); + if (is_deleted === true){ + post.addClass('deleted'); + this._post_deleted = true; + this.getElement().html(gettext('undelete')); + } else if (is_deleted === false){ + post.removeClass('deleted'); + this._post_deleted = false; + this.getElement().html(gettext('delete')); + } +}; + +DeletePostLink.prototype.getDeleteHandler = function(){ + var me = this; + var post_id = this.getPostId(); + return function(){ + var data = { + 'post_id': me.getPostId(), + //todo rename cancel_vote -> undo + 'cancel_vote': me.isPostDeleted() ? true: false + }; + $.ajax({ + type: 'POST', + data: data, + dataType: 'json', + url: askbot['urls']['delete_post'], + cache: false, + success: function(data){ + if (data['success'] == true){ + me.setPostDeleted(data['is_deleted']); + } else { + showMessage(me.getElement(), data['message']); + } + } + }); + }; +}; + +DeletePostLink.prototype.decorate = function(element){ + this._element = element; + this._post_deleted = this.getPostElement().hasClass('deleted'); + this.setHandler(this.getDeleteHandler()); +} + //constructor for the form var EditCommentForm = function(){ WrappedElement.call(this); @@ -1858,6 +1925,13 @@ $(document).ready(function() { var swapper = new QASwapper(); swapper.decorate($(element)); }); + $('[id^="post-id-"]').each(function(idx, element){ + var deleter = new DeletePostLink(); + //confusingly .question-delete matches the answers too need rename + var post_id = element.id.split('-').pop(); + deleter.setPostId(post_id); + deleter.decorate($(element).find('.question-delete')); + }); questionRetagger.init(); socialSharing.init(); }); diff --git a/askbot/skins/common/media/js/user.js b/askbot/skins/common/media/js/user.js index d80adad6..5d205560 100644 --- a/askbot/skins/common/media/js/user.js +++ b/askbot/skins/common/media/js/user.js @@ -18,7 +18,7 @@ $(document).ready(function(){ }; var submit = function(id_list, elements, action_type){ - if (action_type == 'delete' || action_type == 'mark_new' || action_type == 'mark_seen'){ + if (action_type == 'delete' || action_type == 'mark_new' || action_type == 'mark_seen' || action_type == 'remove_flag' || action_type == 'close' || action_type == 'delete_post'){ $.ajax({ type: 'POST', cache: false, @@ -27,7 +27,7 @@ $(document).ready(function(){ url: askbot['urls']['manageInbox'], success: function(response_data){ if (response_data['success'] === true){ - if (action_type == 'delete'){ + if (action_type == 'delete' || action_type == 'remove_flag' || action_type == 'close' || action_type == 'delete_post'){ elements.remove(); } else if (action_type == 'mark_new'){ @@ -61,11 +61,35 @@ $(document).ready(function(){ return; } } + if (action_type == 'close'){ + msg = ngettext('Close this entry?', + 'Close these entries?', data['id_list'].length); + if (confirm(msg) === false){ + return; + } + } + if (action_type == 'remove_flag'){ + msg = ngettext('Remove all flags on this entry?', + 'Remove all flags on these entries?', data['id_list'].length); + if (confirm(msg) === false){ + return; + } + } + if (action_type == 'delete_post'){ + msg = ngettext('Delete this entry?', + 'Delete these entries?', data['id_list'].length); + if (confirm(msg) === false){ + return; + } + } submit(data['id_list'], data['elements'], action_type); }; setupButtonEventHandlers($('#re_mark_seen'), function(){startAction('mark_seen')}); setupButtonEventHandlers($('#re_mark_new'), function(){startAction('mark_new')}); setupButtonEventHandlers($('#re_dismiss'), function(){startAction('delete')}); + setupButtonEventHandlers($('#re_remove_flag'), function(){startAction('remove_flag')}); + setupButtonEventHandlers($('#re_close'), function(){startAction('close')}); + setupButtonEventHandlers($('#re_delete_post'), function(){startAction('delete_post')}); setupButtonEventHandlers( $('#sel_all'), function(){ @@ -92,6 +116,16 @@ $(document).ready(function(){ setCheckBoxesIn('#responses .seen', false); } ); + + setupButtonEventHandlers($('.re_expand'), + function(e){ + e.preventDefault(); + var re_snippet = $(this).find(".re_snippet:first") + var re_content = $(this).find(".re_content:first") + $(re_snippet).slideToggle(); + $(re_content).slideToggle(); + } + ); }); /** diff --git a/askbot/skins/common/templates/authopenid/providers_javascript.html b/askbot/skins/common/templates/authopenid/providers_javascript.html index 0fe72eb3..cd9f56b6 100644 --- a/askbot/skins/common/templates/authopenid/providers_javascript.html +++ b/askbot/skins/common/templates/authopenid/providers_javascript.html @@ -43,9 +43,9 @@ <script> $(document).ready(function(){ if (typeof FB != 'undefined'){ - var ret = FB.init({appId: '{{settings.FACEBOOK_KEY}}', status: true, cookie: true, xfbml: true}); - FB.Event.subscribe('auth.sessionChange', function(response){ - if (response.session) { + var ret = FB.init({appId: '{{settings.FACEBOOK_KEY}}', status: true, cookie: true, xfbml: true, oauth: true}); + FB.Event.subscribe('auth.authResponseChange', function(response){ + if (response.authResponse) { $('#signin-form').submit(); } }); diff --git a/askbot/skins/common/templates/authopenid/signin.html b/askbot/skins/common/templates/authopenid/signin.html index 7fdbe203..30a576cc 100644 --- a/askbot/skins/common/templates/authopenid/signin.html +++ b/askbot/skins/common/templates/authopenid/signin.html @@ -117,6 +117,8 @@ <td><label for="id_new_password">{% trans %}New password{% endtrans %}</label></td>
<td>
{{login_form.new_password}}
+ </td>
+ <td>
<span class="error">{{login_form.new_password.errors[0]}}</span>
</td>
</tr>
@@ -124,6 +126,8 @@ <td><label for="id_new_password_retyped">{% trans %}Please, retype{% endtrans %}</label></td>
<td>
{{login_form.new_password_retyped}}
+ </td>
+ <td>
<span class="error">{{login_form.new_password_retyped.errors[0]}}</span>
</td>
</tr>
diff --git a/askbot/skins/common/templates/question/answer_controls.html b/askbot/skins/common/templates/question/answer_controls.html index bfc36cea..be50d6f4 100644 --- a/askbot/skins/common/templates/question/answer_controls.html +++ b/askbot/skins/common/templates/question/answer_controls.html @@ -1,43 +1,58 @@ -{% set pipe=joiner('<span class="sep">|</span>') %} -<span class="linksopt">{{ pipe() }} - <a class="permant-link" - href="{{ answer.get_absolute_url(question_post=question) }}" - title="{% trans %}answer permanent link{% endtrans %}"> - {% trans %}permanent link{% endtrans %} - </a> - </span> - -{% if request.user|can_edit_post(answer) %}{{ pipe() }} - <span class="action-link"><a class="question-edit" href="{% url edit_answer answer.id %}">{% trans %}edit{% endtrans %}</a></span> +{#<span class="action-link swap-qa"> + <a id="swap-question-with-answer-{{answer.id}}">{% trans %}swap with question{% endtrans %}</a> +</span>uncomment if needed#} +<span class="action-link"> + <a class="permant-link" + href="{{ answer.get_absolute_url(question_post=question) }}" + title="{% trans %}answer permanent link{% endtrans %}"> + {% trans %}permanent link{% endtrans %} + </a> +</span> +{% if request.user.is_authenticated() and + ( + request.user == answer.author or + request.user.is_administrator_or_moderator() + ) +%} +<span class="action-link delete-post"> + <a class="question-delete" + >{% if answer.deleted %}{% trans %}undelete{% endtrans %}{% else %}{% trans %}delete{% endtrans %}{% endif %}</a> +</span> +<span + id="answer-offensive-flag-{{ answer.id }}" + class="action-link offensive-flag" + title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}" +> + <a class="question-flag">{% trans %}flag offensive{% endtrans %} + <span class="darkred">{% if answer.offensive_flag_count > 0 %}({{ answer.offensive_flag_count }}){% endif %}</span> + </a> +</span> +{% if answer.offensive_flag_count > 0 %} +<span + id="answer-offensive-flag-{{ answer.id }}" + class="action-link offensive-flag" + title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}" +> + <a class="question-flag">{% trans %}flag offensive{% endtrans %} ({{ answer.offensive_flag_count }})</a> + </a> +</span> +<span + id="answer-offensive-flag-remove-{{ answer.id }}" + class="action-link offensive-flag" + title="{% trans %}remove offensive flag{% endtrans %}" +> + <a class="question-flag">{% trans %}remove flag{% endtrans %} ({{ answer.offensive_flag_count }})</a> +</span> +{% else %} +<span + id="answer-offensive-flag-{{ answer.id }}" + class="action-link offensive-flag" + title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}" +> + <a class="question-flag">{% trans %}flag offensive{% endtrans %}</a> +</span> {% endif %} -{% if request.user|can_flag_offensive(answer) %}{{ pipe() }} - <span id="answer-offensive-flag-{{ answer.id }}" class="offensive-flag" - title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}"> - <a class="question-flag">{% trans %}flag offensive{% endtrans %}</a> - {% if request.user|can_see_offensive_flags(answer) %} - <span class="darkred">{% if answer.offensive_flag_count > 0 %}({{ answer.offensive_flag_count }}){% endif %}</span> - {% endif %} - </span> - {% elif request.user|can_remove_flag_offensive(answer)%}{{ pipe() }} - <span id="answer-offensive-flag-remove-{{ answer.id }}" class="offensive-flag" - title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}"> - <a class="question-flag">{% trans %}remove flag{% endtrans %}</a> - {% if request.user|can_see_offensive_flags(answer) %} - <span class="darkred">{% if answer.offensive_flag_count > 0 %}({{ answer.offensive_flag_count }}){% endif %}</span> - {% endif %} - </span> +<span class="action-link"> + <a class="question-edit" href="{% url edit_answer answer.id %}">{% trans %}edit{% endtrans %}</a> +</span> {% endif %} -{% if request.user|can_delete_post(answer) %}{{ pipe() }} - {% spaceless %} - <span class="action-link"> - <a class="question-delete" id="answer-delete-link-{{answer.id}}"> - {% if answer.deleted %}{% trans %}undelete{% endtrans %}{% else %}{% trans %}delete{% endtrans %}{% endif %}</a> - </span> - {% endspaceless %} -{% endif %} -{% if settings.ALLOW_SWAPPING_QUESTION_WITH_ANSWER and request.user.is_authenticated() and request.user.is_administrator_or_moderator() %}{{ pipe() }} - <span class="action-link"> - <a id="swap-question-with-answer-{{answer.id}}">{% trans %}swap with question{% endtrans %}</a> - </span> -{% endif %} - diff --git a/askbot/skins/common/templates/question/answer_vote_buttons.html b/askbot/skins/common/templates/question/answer_vote_buttons.html index 68bff3ed..9097fec2 100644 --- a/askbot/skins/common/templates/question/answer_vote_buttons.html +++ b/askbot/skins/common/templates/question/answer_vote_buttons.html @@ -5,7 +5,12 @@ {% else %} src="{{'/images/vote-accepted.png'|media}}" {% endif %} - {% if request.user == question.author or (request.user.is_authenticated() and (request.user.is_moderator() or request.user.is_administrator())) %} + {% if request.user.is_authenticated() and + ( + request.user == question.author or + request.user.is_administrator_or_moderator() + ) + %} alt="{% trans %}mark this answer as correct (click again to undo){% endtrans %}" title="{% trans %}mark this answer as correct (click again to undo){% endtrans %}" {% else %} diff --git a/askbot/skins/common/templates/question/question_controls.html b/askbot/skins/common/templates/question/question_controls.html index 5658d559..4710559d 100644 --- a/askbot/skins/common/templates/question/question_controls.html +++ b/askbot/skins/common/templates/question/question_controls.html @@ -1,39 +1,43 @@ -{% set pipe=joiner('<span class="sep">|</span>') %} -{% if request.user|can_edit_post(question) %}{{ pipe() }} - <a class="question-edit" href="{% url edit_question question.id %}">{% trans %}edit{% endtrans %}</a> -{% endif %} -{% if request.user|can_retag_question(question) %}{{ pipe() }} - <a id="retag" class="question-retag"href="{% url retag_question question.id %}">{% trans %}retag{% endtrans %}</a> - <script type="text/javascript"> - var retagUrl = "{% url retag_question question.id %}"; - </script> -{% endif %} -{% if thread.closed %} - {% if request.user|can_reopen_question(question) %}{{ pipe() }} +{% if request.user.is_authenticated() and + ( + request.user == question.author or + request.user.is_administrator_or_moderator() + ) +%} + <a + id="question-delete-link-{{question.id}}" + class="question-delete" + >{% if question.deleted %}{% trans %}undelete{% endtrans %}{% else %}{% trans %}delete{% endtrans %}{% endif %}</a> + {% if thread.closed %} <a class="question-close" href="{% url reopen question.id %}">{% trans %}reopen{% endtrans %}</a> - {% endif %} -{% else %} - {% if request.user|can_close_question(question) %}{{ pipe() }} + {% else %} <a class="question-close" href="{% url close question.id %}">{% trans %}close{% endtrans %}</a> {% endif %} -{% endif %} -{% if request.user|can_flag_offensive(question) %}{{ pipe() }} - <span id="question-offensive-flag-{{ question.id }}" class="offensive-flag" - title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}"> - <a class="question-flag">{% trans %}flag offensive{% endtrans %}</a> - {% if request.user|can_see_offensive_flags(question) %} - <span class="darkred">{% if question.offensive_flag_count > 0 %}({{ question.offensive_flag_count }}){% endif %}</span> - {% endif %} - </span> - {% elif request.user|can_remove_flag_offensive(question)%}{{ pipe() }} - <span id="question-offensive-flag-remove-{{ question.id }}" class="offensive-flag" - title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}"> - <a class="question-flag">{% trans %}remove flag{% endtrans %}</a> - {% if request.user|can_see_offensive_flags(question) %} - <span class="darkred">{% if question.offensive_flag_count > 0 %}({{ question.offensive_flag_count }}){% endif %}</span> - {% endif %} - </span> -{% endif %} -{% if request.user|can_delete_post(question) %}{{ pipe() }} - <a id="question-delete-link-{{question.id}}" class="question-delete">{% if question.deleted %}{% trans %}undelete{% endtrans %}{% else %}{% trans %}delete{% endtrans %}{% endif %}</a> + {% if question.offensive_flag_count > 0 %} + <span + id="question-offensive-flag-{{ question.id }}" class="offensive-flag" + title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}" + > + <a class="question-flag">{% trans %}flag offensive{% endtrans %} {{ question.offensive_flag_count }})</a> + </span> + <span + id="question-offensive-flag-remove-{{ question.id }}" + class="offensive-flag" + title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}" + > + <a class="question-flag">{% trans %}remove flag{% endtrans %} ({{ question.offensive_flag_count }})</a> + </span> + {% else %} + <span + id="question-offensive-flag-{{ question.id }}" class="offensive-flag" + title="{% trans %}report as offensive (i.e containing spam, advertising, malicious text, etc.){% endtrans %}" + > + <a class="question-flag">{% trans %}flag offensive{% endtrans %}</a> + </span> + {% endif %} + <script type="text/javascript"> + var retagUrl = "{% url retag_question question.id %}"; + </script> + <a id="retag" class="question-retag"href="{% url retag_question question.id %}">{% trans %}retag{% endtrans %}</a> + <a class="question-edit" href="{% url edit_question question.id %}">{% trans %}edit{% endtrans %}</a> {% endif %} diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css index d52c4f11..737dcdd2 100644 --- a/askbot/skins/default/media/style/style.css +++ b/askbot/skins/default/media/style/style.css @@ -3153,3 +3153,13 @@ pre.prettyprint { #leading-sidebar { float: left; } + +a.re_expand{ + color: #616161; + text-decoration:none; +} + +a.re_expand .re_content{ + display:none; + margin-left:77px; +}
\ No newline at end of file diff --git a/askbot/skins/default/media/style/style.less b/askbot/skins/default/media/style/style.less index 7b564d8a..e8e5a5d8 100644 --- a/askbot/skins/default/media/style/style.less +++ b/askbot/skins/default/media/style/style.less @@ -1623,8 +1623,8 @@ ul#related-tags li { margin-bottom:8px; a { - color: #777; - padding: 0px 3px 3px 22px; + color: #777; + padding: 0px 7px 3px 18px; cursor: pointer; border: none; font-size:12px; @@ -1653,7 +1653,7 @@ ul#related-tags li { .post-controls, .answer-controls{ .question-delete{ background: url(../images/delete.png) no-repeat center left; - padding-left:16px; + padding-left:11px; } .question-flag{ background: url(../images/flag.png) no-repeat center left; diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html index dcbe7874..af0826ab 100644 --- a/askbot/skins/default/templates/macros.html +++ b/askbot/skins/default/templates/macros.html @@ -317,7 +317,7 @@ for the purposes of the AJAX comment editor #} {% endfor %} </div> <div class="controls"> - {% set can_post = user|can_post_comment(post) %} + {% set can_post = user.is_authenticated() and user.can_post_comment(post) %} {% if show_post == post and show_comment %} {% if show_comment_position > max_comments %} {{ diff --git a/askbot/skins/default/templates/question.html b/askbot/skins/default/templates/question.html index 33c0e11f..b2462faf 100644 --- a/askbot/skins/default/templates/question.html +++ b/askbot/skins/default/templates/question.html @@ -6,7 +6,7 @@ {% endblock %} {% block keywords %}{{thread.tagname_meta_generator()}}{% endblock %} {% block forestyle %} - <link rel="canonical" href="{{settings.APP_URL}}{{question.get_absolute_url()}}" /> + <link rel="canonical" href="{{settings.APP_URL|strip_path}}{{question.get_absolute_url()}}" /> <link rel="stylesheet" type="text/css" href="{{'/js/wmd/wmd.css'|media}}" /> {% endblock %} {% block content %} diff --git a/askbot/skins/default/templates/question/answer_card.html b/askbot/skins/default/templates/question/answer_card.html index 60317559..d71131a8 100644 --- a/askbot/skins/default/templates/question/answer_card.html +++ b/askbot/skins/default/templates/question/answer_card.html @@ -4,7 +4,7 @@ <a class="old_answer_id_anchor" name="{{ answer.old_answer_id }}"></a> {% endif %} <div - id="answer-container-{{ answer.id }}" + id="post-id-{{ answer.id }}" class="{{ macros.answer_classes(answer, question) }}"> <div class="vote-buttons"> {# ==== START: question/answer_vote_buttons.html ==== #} diff --git a/askbot/skins/default/templates/question/javascript.html b/askbot/skins/default/templates/question/javascript.html index 9c8e7fc6..a5a53e39 100644 --- a/askbot/skins/default/templates/question/javascript.html +++ b/askbot/skins/default/templates/question/javascript.html @@ -16,6 +16,7 @@ askbot['urls']['user_signin'] = '{{ settings.LOGIN_URL }}'; askbot['urls']['swap_question_with_answer'] = '{% url swap_question_with_answer %}'; askbot['urls']['upvote_comment'] = '{% url upvote_comment %}'; + askbot['urls']['delete_post'] = '{% url delete_post %}'; askbot['messages']['addComment'] = '{% trans %}add comment{% endtrans %}'; {% if settings.SAVE_COMMENT_ON_ENTER %} askbot['settings']['saveCommentOnEnter'] = true; diff --git a/askbot/skins/default/templates/question/question_card.html b/askbot/skins/default/templates/question/question_card.html index ff4ada1d..7077a8d1 100644 --- a/askbot/skins/default/templates/question/question_card.html +++ b/askbot/skins/default/templates/question/question_card.html @@ -1,4 +1,3 @@ - <div class="vote-buttons"> {# ==== BEGIN: question/question_vote_buttons.html ==== #} {% include "question/question_vote_buttons.html" %} @@ -7,13 +6,13 @@ {% include "question/share_buttons.html" %} {# ==== END: question/share_buttons.html ==== #} </div> -<div class="question-content"> +<div id="post-id-{{question.id}}" class="question-content{% if question.deleted %} deleted{% endif %}"> <h1><a href="{{ question.get_absolute_url() }}">{{ thread.get_title(question)|escape }}</a></h1> {% include "question/question_tags.html" %} {# ==== END: question/question_tags.html" #} - <div id="question-table" {% if question.deleted %}class="deleted"{%endif%}> + <div id="question-table"> <div class="question-body"> <div class="post-update-info-container"> {# ==== START: "question/question_author_info.html" #} diff --git a/askbot/skins/default/templates/question/sharing_prompt_phrase.html b/askbot/skins/default/templates/question/sharing_prompt_phrase.html index f7bd20af..2e68d1f3 100644 --- a/askbot/skins/default/templates/question/sharing_prompt_phrase.html +++ b/askbot/skins/default/templates/question/sharing_prompt_phrase.html @@ -1,4 +1,4 @@ -{% set question_url=settings.APP_URL+question.get_absolute_url()|urlencode %} +{% set question_url=(settings.APP_URL|strip_path + question.get_absolute_url())|urlencode %} <h2 class="share-question">{% trans %}Know someone who can answer? Share a <a href="{{ question_url }}">link</a> to this question via{% endtrans %} {% if settings.ENABLE_SHARING_TWITTER %}{{ macros.share(site = 'twitter', site_label = 'Twitter') }},{% endif %} {% if settings.ENABLE_SHARING_FACEBOOK %}{{ macros.share(site = 'facebook', site_label = 'Facebook') }},{% endif %} diff --git a/askbot/skins/default/templates/question/sidebar.html b/askbot/skins/default/templates/question/sidebar.html index bc6a58c9..08c043a6 100644 --- a/askbot/skins/default/templates/question/sidebar.html +++ b/askbot/skins/default/templates/question/sidebar.html @@ -37,8 +37,6 @@ </p> </div> </div> -{% cache 0 "questions_tags" questions_tags question.id language_code %} - {% if settings.SIDEBAR_QUESTION_SHOW_META %} <div class="box statsWidget"> @@ -55,7 +53,6 @@ </p> </div> {% endif %} -{% endcache %} {% if similar_threads.data and settings.SIDEBAR_QUESTION_SHOW_RELATED %} {#% cache 1800 "related_questions" related_questions question.id language_code %#} diff --git a/askbot/skins/default/templates/question_widget.html b/askbot/skins/default/templates/question_widget.html index 65ee8b64..9d32294b 100644 --- a/askbot/skins/default/templates/question_widget.html +++ b/askbot/skins/default/templates/question_widget.html @@ -11,7 +11,7 @@ <div id="container"> <ul> {% for thread in threads %} - <li><a href="{{settings.APP_URL}}{{ thread.get_absolute_url() }}"> + <li><a href="{{settings.APP_URL|strip_path}}{{ thread.get_absolute_url() }}"> {{ thread.title|escape }}</a></li> {% endfor %} </ul> diff --git a/askbot/skins/default/templates/user_profile/user_inbox.html b/askbot/skins/default/templates/user_profile/user_inbox.html index e7e3dbfe..f70f1884 100644 --- a/askbot/skins/default/templates/user_profile/user_inbox.html +++ b/askbot/skins/default/templates/user_profile/user_inbox.html @@ -56,6 +56,18 @@ inbox_section - forum|flags <button id="re_dismiss">{% trans %}dismiss{% endtrans %}</button> </div> {% endif %} + {% if inbox_section == 'flags' %} + <div id="re_tools"> + <strong>{% trans %}select:{% endtrans %}</strong> + <a id="sel_all">{% trans %}all{% endtrans %}</a> | + <a id="sel_seen">{% trans %}seen{% endtrans %}</a> | + <a id="sel_new">{% trans %}new{% endtrans %}</a> | + <a id="sel_none">{% trans %}none{% endtrans %}</a><br /> + <button id="re_remove_flag">{% trans %}remove flags{% endtrans %}</button> + <button id="re_close">{% trans %}close{% endtrans %}</button> + <button id="re_delete_post">{% trans %}delete post{% endtrans %}</button> + </div> + {% endif %} <div id="responses"> {% for response in responses %} <div class="response-parent"> @@ -63,7 +75,7 @@ inbox_section - forum|flags <strong>"{{ response.response_title.strip()|escape}}"</strong> </p> <div id="re_{{response.id}}" class="re{% if response.is_new %} new highlight{% else %} seen{% endif %}"> - {% if inbox_section == 'forum' %}<input type="checkbox" />{% endif %} + <input type="checkbox" /> <div class="face"> {{ macros.gravatar(response.user, 48) }} </div> @@ -71,13 +83,20 @@ inbox_section - forum|flags <a style="text-decoration:none;" href="{{ response.response_url }}"> {{ response.response_type }} ({{ response.timestamp|diff_date(True) }}):<br/> - {{ response.response_snippet}} + {% if inbox_section != 'flags' %} + {{ response.response_snippet }} + {% endif %} </a> + {% if inbox_section == 'flags' %} + <a class="re_expand" href="{{ response.response_url }}"> + <div class="re_snippet">{{ response.response_snippet }}</div> + <div class="re_content">{{ response.response_content }}</div></a> + {% endif %} </div> {% if response.nested_responses %} {%for nested_response in response.nested_responses %} <div id="re_{{nested_response.id}}" class="re{% if nested_response.is_new %} new highlight{% else %} seen{% endif %}"> - {% if inbox_section == 'forum' %}<input type="checkbox" />{% endif %} + <input type="checkbox" /> <div class="face"> {{ macros.gravatar(nested_response.user, 48) }} </div> @@ -85,8 +104,15 @@ inbox_section - forum|flags <a style="text-decoration:none;" href="{{ nested_response.response_url }}"> {{ nested_response.response_type }} ({{ nested_response.timestamp|diff_date(True) }}):<br/> - {{ nested_response.response_snippet}} + {% if inbox_section != 'flags' %} + {{ nested_response.response_snippet }} + {% endif %} </a> + {% if inbox_section == 'flags' %} + <a class="re_expand" href="{{ nested_response.response_url }}"> + <div class="re_snippet">{{ nested_response.response_snippet }}</div> + <div class="re_content">{{ nested_response.response_content }}</div></a> + {% endif %} </div> {%endfor%} {%endif%} @@ -96,7 +122,6 @@ inbox_section - forum|flags </div> {% endblock %} {% block userjs %} - <script type="text/javascript" src="{{'/js/user.js'|media}}"></script> <script type="text/javascript"> var askbot = askbot || {}; askbot['urls'] = askbot['urls'] || {}; diff --git a/askbot/tasks.py b/askbot/tasks.py index fefe99f5..634befb9 100644 --- a/askbot/tasks.py +++ b/askbot/tasks.py @@ -22,15 +22,15 @@ import traceback from django.contrib.contenttypes.models import ContentType from celery.decorators import task -from askbot.models import Activity -from askbot.models import User +from askbot.models import Activity, Post, Thread, User from askbot.models import send_instant_notifications_about_activity_in_post +from askbot.models.badges import award_badges_signal # TODO: Make exceptions raised inside record_post_update_celery_task() ... # ... propagate upwards to test runner, if only CELERY_ALWAYS_EAGER = True # (i.e. if Celery tasks are not deferred but executed straight away) -@task(ignore_results = True) +@task(ignore_result = True) def record_post_update_celery_task( post_id, post_content_type_id, @@ -152,3 +152,32 @@ def record_post_update( post = post, recipients = notification_subscribers, ) + +@task(ignore_result = True) +def record_question_visit( + question_post_id = None, + user_id = None, + update_view_count = False): + """celery task which records question visit by a person + updates view counter, if necessary, + and awards the badges associated with the + question visit + """ + #1) maybe update the view count + question_post = Post.objects.get(id = question_post_id) + if update_view_count: + question_post.thread.increase_view_count() + + #2) question view count per user and clear response displays + user = User.objects.get(id = user_id) + if user.is_authenticated(): + #get response notifications + user.visit_question(question_post) + + #3) send award badges signal for any badges + #that are awarded for question views + award_badges_signal.send(None, + event = 'view_question', + actor = user, + context_object = question_post, + ) diff --git a/askbot/templatetags/extra_filters_jinja.py b/askbot/templatetags/extra_filters_jinja.py index 52180da5..b7fbc5f0 100644 --- a/askbot/templatetags/extra_filters_jinja.py +++ b/askbot/templatetags/extra_filters_jinja.py @@ -14,6 +14,7 @@ from askbot.conf import settings as askbot_settings from django.conf import settings as django_settings from askbot.skins import utils as skin_utils from askbot.utils import functions +from askbot.utils import url_utils from askbot.utils.slug import slugify from askbot.shims.django_shims import ResolverMatch @@ -35,6 +36,11 @@ def absolutize_urls_func(text): absolutize_urls = register.filter(absolutize_urls_func) @register.filter +def strip_path(url): + """removes path part of the url""" + return url_utils.strip_path(url) + +@register.filter def clean_login_url(url): """pass through, unless user was originally on the logout page""" try: diff --git a/askbot/urls.py b/askbot/urls.py index 640cb51e..1ab3ea5d 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -130,6 +130,11 @@ urlpatterns = patterns('', name = 'upvote_comment' ), url(#ajax only + r'^post/delete/$', + views.commands.delete_post, + name = 'delete_post' + ), + url(#ajax only r'^post_comments/$', views.writers.post_comments, name='post_comments' diff --git a/askbot/utils/console.py b/askbot/utils/console.py index 496bbd65..fe6ad29e 100644 --- a/askbot/utils/console.py +++ b/askbot/utils/console.py @@ -74,3 +74,43 @@ def print_progress(elapsed, total, nowipe = False): in-place""" output = '%6.2f%%' % (100 * float(elapsed)/float(total)) print_action(output, nowipe) + +class ProgressBar(object): + """A wrapper for an iterator, that prints + a progress bar along the way of iteration + """ + def __init__(self, iterable, length, message = ''): + self.iterable = iterable + self.length = length + self.counter = float(0) + self.barlen = 60 + self.progress = '' + if message and length > 0: + print message + + + def __iter__(self): + return self + + def next(self): + + try: + result = self.iterable.next() + except StopIteration: + if self.length > 0: + sys.stdout.write('\n') + raise + + sys.stdout.write('\b'*len(self.progress)) + + if self.length < self.barlen: + sys.stdout.write('-'*(self.barlen/self.length)) + elif int(self.counter) % (self.length / self.barlen) == 0: + sys.stdout.write('-') + + self.progress = ' %.2f%%' % (100 * (self.counter/self.length)) + sys.stdout.write(self.progress) + sys.stdout.flush() + + self.counter += 1 + return result diff --git a/askbot/utils/url_utils.py b/askbot/utils/url_utils.py index 0ebce394..6027d096 100644 --- a/askbot/utils/url_utils.py +++ b/askbot/utils/url_utils.py @@ -1,6 +1,18 @@ +import urlparse from django.core.urlresolvers import reverse from django.conf import settings +def strip_path(url): + """srips path, params and hash fragments of the url""" + purl = urlparse.urlparse(url) + return urlparse.urlunparse( + urlparse.ParseResult( + purl.scheme, + purl.netloc, + '', '', '', '' + ) + ) + def get_login_url(): """returns internal login url if django_authopenid is used, or @@ -27,6 +39,6 @@ def get_logout_redirect_url(): if 'askbot.deps.django_authopenid' in settings.INSTALLED_APPS: return reverse('logout') elif hasattr(settings, 'LOGOUT_REDIRECT_URL'): - return settigs.LOGOUT_REDIRECT_URL + return settings.LOGOUT_REDIRECT_URL else: return reverse('index') diff --git a/askbot/views/commands.py b/askbot/views/commands.py index 7db27ef2..b95143b0 100644 --- a/askbot/views/commands.py +++ b/askbot/views/commands.py @@ -41,7 +41,7 @@ def manage_inbox(request): post_data = simplejson.loads(request.raw_post_data) if request.user.is_authenticated(): activity_types = const.RESPONSE_ACTIVITY_TYPES_FOR_DISPLAY - activity_types += (const.TYPE_ACTIVITY_MENTION, ) + activity_types += (const.TYPE_ACTIVITY_MENTION, const.TYPE_ACTIVITY_MARK_OFFENSIVE,) user = request.user memo_set = models.ActivityAuditStatus.objects.filter( id__in = post_data['memo_list'], @@ -56,6 +56,18 @@ def manage_inbox(request): memo_set.update(status = models.ActivityAuditStatus.STATUS_NEW) elif action_type == 'mark_seen': memo_set.update(status = models.ActivityAuditStatus.STATUS_SEEN) + elif action_type == 'remove_flag': + for memo in memo_set: + request.user.flag_post(post = memo.activity.content_object, cancel_all = True) + elif action_type == 'close': + for memo in memo_set: + if memo.activity.content_object.post_type == "question": + request.user.close_question(question = memo.activity.content_object, reason = 7) + memo.delete() + elif action_type == 'delete_post': + for memo in memo_set: + request.user.delete_post(post = memo.activity.content_object) + memo.delete() else: raise exceptions.PermissionDenied( _('Oops, apologies - there was some error') @@ -584,6 +596,28 @@ def upvote_comment(request): raise ValueError return {'score': comment.score} +@csrf.csrf_exempt +@decorators.ajax_only +@decorators.post_only +def delete_post(request): + if request.user.is_anonymous(): + raise exceptions.PermissionDenied(_('Please sign in to delete/restore posts')) + form = forms.VoteForm(request.POST) + if form.is_valid(): + post_id = form.cleaned_data['post_id'] + post = get_object_or_404( + models.Post, + post_type__in = ('question', 'answer'), + id = post_id + ) + if form.cleaned_data['cancel_vote']: + request.user.restore_post(post) + else: + request.user.delete_post(post) + else: + raise ValueError + return {'is_deleted': post.deleted} + #askbot-user communication system @csrf.csrf_exempt def read_message(request):#marks message a read diff --git a/askbot/views/readers.py b/askbot/views/readers.py index cccfce67..a6f65e28 100644 --- a/askbot/views/readers.py +++ b/askbot/views/readers.py @@ -30,7 +30,6 @@ from askbot.utils.diff import textDiff as htmldiff from askbot.forms import AnswerForm, ShowQuestionForm from askbot import models from askbot import schedules -from askbot.models.badges import award_badges_signal from askbot.models.tag import Tag from askbot import const from askbot.utils import functions @@ -428,7 +427,9 @@ def question(request, id):#refactor - long subroutine. display question body, an # TODO: Add unit test to catch the bug where precache_comments() is called above (before) reordering the accepted answer to the top #Post.objects.precache_comments(for_posts=[question_post] + answers, visitor=request.user) - if thread.accepted_answer: # Put the accepted answer to front + if thread.accepted_answer and thread.accepted_answer.deleted == False: + #Put the accepted answer to front + #the second check is for the case when accepted answer is deleted answers.remove(thread.accepted_answer) answers.insert(0, thread.accepted_answer) @@ -467,11 +468,9 @@ def question(request, id):#refactor - long subroutine. display question body, an last_seen = request.session['question_view_times'].get(question_post.id, None) - updated_when, updated_who = thread.get_last_update_info() - - if updated_who != request.user: + if thread.last_activity_by != request.user: if last_seen: - if last_seen < updated_when: + if last_seen < thread.last_activity_at: update_view_count = True else: update_view_count = True @@ -479,21 +478,13 @@ def question(request, id):#refactor - long subroutine. display question body, an request.session['question_view_times'][question_post.id] = \ datetime.datetime.now() - if update_view_count: - thread.increase_view_count() - - #2) question view count per user and clear response displays - if request.user.is_authenticated(): - #get response notifications - request.user.visit_question(question_post) - - #3) send award badges signal for any badges - #that are awarded for question views - award_badges_signal.send(None, - event = 'view_question', - actor = request.user, - context_object = question_post, - ) + #2) run the slower jobs in a celery task + from askbot import tasks + tasks.record_question_visit.delay( + question_post_id = question_post.id, + user_id = request.user.id, + update_view_count = update_view_count + ) paginator_data = { 'is_paginated' : (objects_list.count > const.ANSWERS_PAGE_SIZE), diff --git a/askbot/views/users.py b/askbot/views/users.py index 28561bf4..b38a54c8 100644 --- a/askbot/views/users.py +++ b/askbot/views/users.py @@ -597,6 +597,7 @@ def user_responses(request, user, context): 'response_type': memo.activity.get_activity_type_display(), 'response_id': memo.activity.question.id, 'nested_responses': [], + 'response_content': memo.activity.content_object.html, } response_list.append(response) @@ -617,6 +618,7 @@ def user_responses(request, user, context): last_response_index = i response_list = filtered_response_list + response_list.sort(lambda x,y: cmp(y['timestamp'], x['timestamp'])) filtered_response_list = list() diff --git a/askbot/views/writers.py b/askbot/views/writers.py index 9de14517..7ebb1991 100644 --- a/askbot/views/writers.py +++ b/askbot/views/writers.py @@ -12,6 +12,7 @@ import random import sys import tempfile import time +import urlparse from django.core.files.storage import get_storage_class from django.shortcuts import get_object_or_404 from django.contrib.auth.decorators import login_required @@ -99,16 +100,29 @@ def upload(request):#ajax upload file to a question or answer if error == '': result = 'Good' file_url = file_storage.url(new_file_name) + parsed_url = urlparse.urlparse(file_url) + file_url = urlparse.urlunparse( + urlparse.ParseResult( + parsed_url.scheme, + parsed_url.netloc, + parsed_url.path, + '', '', '' + ) + ) else: result = '' file_url = '' - data = simplejson.dumps({ - 'result': result, - 'error': error, - 'file_url': file_url - }) - return HttpResponse(data, mimetype = 'application/json') + #data = simplejson.dumps({ + # 'result': result, + # 'error': error, + # 'file_url': file_url + #}) + #return HttpResponse(data, mimetype = 'application/json') + xml_template = "<result><msg><![CDATA[%s]]></msg><error><![CDATA[%s]]></error><file_url>%s</file_url></result>" + xml = xml_template % (result, error, file_url) + + return HttpResponse(xml, mimetype="application/xml") def __import_se_data(dump_file): """non-view function that imports the SE data |