diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2010-08-07 00:14:28 -0400 |
---|---|---|
committer | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2010-08-07 00:14:28 -0400 |
commit | 72da3d527c454bd3e3fee4a94370c6b78289f938 (patch) | |
tree | a4a22224295a4d63fa8f25ae9dca25545e603c88 | |
parent | d99947cc63fdd650434e5dbfe0462cd8a2a28233 (diff) | |
download | askbot-72da3d527c454bd3e3fee4a94370c6b78289f938.tar.gz askbot-72da3d527c454bd3e3fee4a94370c6b78289f938.tar.bz2 askbot-72da3d527c454bd3e3fee4a94370c6b78289f938.zip |
moderation rules apply to post deletion, added new boolean setting to always show all UI functions
-rw-r--r-- | askbot/auth.py | 8 | ||||
-rw-r--r-- | askbot/conf/skin_general_settings.py | 22 | ||||
-rw-r--r-- | askbot/models/__init__.py | 75 | ||||
-rwxr-xr-x | askbot/skins/default/media/js/com.cnprog.post.js | 20 | ||||
-rw-r--r-- | askbot/templatetags/extra_filters.py | 32 | ||||
-rw-r--r-- | askbot/tests/permission_assertion_tests.py | 150 | ||||
-rw-r--r-- | askbot/views/commands.py | 36 |
7 files changed, 269 insertions, 74 deletions
diff --git a/askbot/auth.py b/askbot/auth.py index fb1832ce..3e24d113 100644 --- a/askbot/auth.py +++ b/askbot/auth.py @@ -418,14 +418,12 @@ def onDeleteCanceled(post, user, timestamp=None): post.deleted_by = 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 - ) Question.objects.update_answer_count(post.question) elif isinstance(post, Question): + #todo: make sure that these tags actually exist + #some may have since been deleted for good + #or merged into others for tag in list(post.tags.all()): if tag.used_count == 1 and tag.deleted: tag.deleted = False diff --git a/askbot/conf/skin_general_settings.py b/askbot/conf/skin_general_settings.py index b96f3424..56102453 100644 --- a/askbot/conf/skin_general_settings.py +++ b/askbot/conf/skin_general_settings.py @@ -2,16 +2,33 @@ General skin settings """ from askbot.conf.settings_wrapper import settings -from askbot.deps.livesettings import ConfigurationGroup, StringValue, IntegerValue +from askbot.deps.livesettings import ConfigurationGroup +from askbot.deps.livesettings import StringValue, IntegerValue, BooleanValue from django.utils.translation import ugettext as _ from askbot.skins.utils import get_skin_choices GENERAL_SKIN_SETTINGS = ConfigurationGroup( 'GENERAL_SKIN_SETTINGS', - _('Skin: general settings'), + _('Skin and User Interface settings'), ) settings.register( + BooleanValue( + GENERAL_SKIN_SETTINGS, + 'ALWAYS_SHOW_ALL_UI_FUNCTIONS', + default = False, + description = _('Show all UI functions to all users'), + help_text = _( + 'If checked, all forum functions ' + 'will be shown to users, regardless of their ' + 'reputation. However to use those functions, ' + 'moderation rules, reputation and other limits ' + 'will still apply.' + ) + ) +) + +settings.register( StringValue( GENERAL_SKIN_SETTINGS, 'ASKBOT_DEFAULT_SKIN', @@ -35,4 +52,3 @@ settings.register( ) ) ) - diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index e4e1b2b9..f16fa744 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -8,6 +8,7 @@ from askbot.search.indexer import create_fulltext_indexes from django.db.models import signals as django_signals from django.template import loader, Context from django.utils.translation import ugettext as _ +from django.utils.translation import ungettext from django.contrib.auth.models import User from django.template.defaultfilters import slugify from django.utils.safestring import mark_safe @@ -275,9 +276,56 @@ def user_assert_can_edit_post(self, post = None): min_rep_setting = min_rep_setting ) - def user_assert_can_delete_post(self, post = None): - #todo: move here advanced logic to authorize deletion + if isinstance(post, Question): + self.assert_can_delete_question(question = post) + elif isinstance(post, Answer): + self.assert_can_delete_answer(answer = post) + elif isinstance(post, Comment): + self.assert_can_delete_comment(comment = post) + +def user_assert_can_restore_post(self, post = None): + """can_restore_rule is the same as can_delete + """ + self.assert_can_delete_post(post = post) + +def user_assert_can_delete_question(self, question = None): + """rules are the same as to delete answer, + except if question has answers already, when owner + cannot delete unless s/he is and adinistrator or moderator + """ + + #cheating here. can_delete_answer wants argument named + #"question", so the argument name is skipped + self.assert_can_delete_answer(question) + if self == question.get_owner(): + #if there are answers by other people, + #then deny, unless user in admin or moderator + answer_count = question.answers.exclude( + author = self, + ).exclude( + score__lte = 0 + ).count() + + if answer_count > 0: + if self.is_administrator() or self.is_moderator(): + return + else: + msg = ungettext( + 'Sorry, cannot delete your question since it ' + \ + 'has an upvoted answer posted by someone else', + 'Sorry, cannot delete your question since it ' + \ + 'has some upvoted answers posted by other users', + answer_count + ) + raise django_exceptions.PermissionDenied(msg) + + +def user_assert_can_delete_answer(self, answer = None): + """intentionally use "post" word in the messages + instead of "answer", because this logic also applies to + assert on deleting question (in addition to some special rules) + """ blocked_error_message = _( 'Sorry, since your account is blocked ' + \ 'you cannot delete posts' @@ -295,7 +343,7 @@ def user_assert_can_delete_post(self, post = None): _assert_user_can( user = self, - post = post, + post = answer, owner_can = True, blocked_error_message = blocked_error_message, suspended_error_message = suspended_error_message, @@ -304,14 +352,6 @@ def user_assert_can_delete_post(self, post = None): ) -def user_assert_can_delete_question(self, question = None): - self.assert_can_delete_post(post = question) - - -def user_assert_can_delete_answer(self, answer = None): - self.assert_can_delete_post(post = answer) - - def user_assert_can_close_question(self, question = None): assert(isinstance(question, Question) == True) blocked_error_message = _( @@ -556,6 +596,17 @@ def user_delete_post( else: raise TypeError('either Comment, Question or Answer expected') +def user_restore_post( + self, + post = None, + timestamp = None + ): + self.assert_can_restore_post(post) + if isinstance(post, Question) or isinstance(post, Answer): + auth.onDeleteCanceled(self, post, timestamp) + else: + raise NotImplementedError() + def user_post_question( self, title = None, @@ -1040,6 +1091,7 @@ User.add_to_class('get_unused_votes_today', user_get_unused_votes_today) User.add_to_class('delete_comment', user_delete_comment) User.add_to_class('delete_question', user_delete_question) User.add_to_class('delete_answer', user_delete_answer) +User.add_to_class('restore_post', user_restore_post) User.add_to_class('close_question', user_close_question) #assertions @@ -1055,6 +1107,7 @@ User.add_to_class('assert_can_flag_offensive', user_assert_can_flag_offensive) User.add_to_class('assert_can_retag_questions', user_assert_can_retag_questions) #todo: do we need assert_can_delete_post User.add_to_class('assert_can_delete_post', user_assert_can_delete_post) +User.add_to_class('assert_can_restore_post', user_assert_can_restore_post) User.add_to_class('assert_can_delete_comment', user_assert_can_delete_comment) User.add_to_class('assert_can_delete_answer', user_assert_can_delete_answer) User.add_to_class('assert_can_delete_question', user_assert_can_delete_question) diff --git a/askbot/skins/default/media/js/com.cnprog.post.js b/askbot/skins/default/media/js/com.cnprog.post.js index b82be4ee..5ad07f5d 100755 --- a/askbot/skins/default/media/js/com.cnprog.post.js +++ b/askbot/skins/default/media/js/com.cnprog.post.js @@ -356,19 +356,7 @@ var Vote = function(){ }; var callback_remove = function(object, voteType, data){ - if (data.allowed == "0" && data.success == "0"){ - showMessage( - object, - removeAnonymousMessage.replace( - "{{QuestionID}}", - questionId - ).replace( - '{{questionSlug}}', - '' - ) - ); - } - else if (data.success == "1"){ + if (data.success == "1"){ if (voteType == VoteType.removeQuestion){ window.location.href = scriptUrl + $.i18n._("questions/"); } @@ -384,7 +372,10 @@ var Vote = function(){ showMessage(object, recoveredMessage); } } - } + } + else { + showMessage(object, data.message) + } }; return { @@ -721,6 +712,7 @@ function createComments(type) { par.remove(); }, error: function(xhr, textStatus, exception) { + jImg.show(); showMessage(jImg, xhr.responseText); }, dataType: "json" diff --git a/askbot/templatetags/extra_filters.py b/askbot/templatetags/extra_filters.py index 688c539e..6c52be64 100644 --- a/askbot/templatetags/extra_filters.py +++ b/askbot/templatetags/extra_filters.py @@ -24,6 +24,10 @@ def make_template_filter_from_permission_assertion( permission assertion """ def filter_function(user, post): + + if askbot_settings.ALWAYS_SHOW_ALL_UI_FUNCTIONS: + return True + if user.is_anonymous(): return False @@ -64,14 +68,20 @@ can_post_comment = make_template_filter_from_permission_assertion( filter_name = 'can_post_comment' ) +can_close_question = make_template_filter_from_permission_assertion( + assertion_name = 'assert_can_close_question', + filter_name = 'can_close_question' + ) + can_delete_comment = make_template_filter_from_permission_assertion( assertion_name = 'assert_can_delete_comment', filter_name = 'can_delete_comment' ) -can_close_question = make_template_filter_from_permission_assertion( - assertion_name = 'assert_can_close_question', - filter_name = 'can_close_question' +#this works for questions, answers and comments +can_delete_post = make_template_filter_from_permission_assertion( + assertion_name = 'assert_can_delete_post', + filter_name = 'can_delete_post' ) @register.filter @@ -119,22 +129,6 @@ def can_reopen_question(user, question): return auth.can_reopen_question(user, question) @register.filter -def can_delete_post(user, post): - if user.is_anonymous(): - return False - try: - if isinstance(post, models.Question): - user.assert_can_delete_question(question = post) - return True - elif isinstance(post, models.Answer): - user.assert_can_delete_answer(answer = post) - return True - else: - return False - except django_exceptions.PermissionDenied: - return False - -@register.filter def can_view_user_edit(request_user, target_user): return auth.can_view_user_edit(request_user, target_user) diff --git a/askbot/tests/permission_assertion_tests.py b/askbot/tests/permission_assertion_tests.py index 884f3f0e..171d30c3 100644 --- a/askbot/tests/permission_assertion_tests.py +++ b/askbot/tests/permission_assertion_tests.py @@ -153,6 +153,156 @@ class SeeOffensiveFlagsPermissionAssertionTests(utils.AskbotTestCase): ) ) +class DeleteAnswerPermissionAssertionTests(utils.AskbotTestCase): + + def setUp(self): + self.create_user() + self.create_user(username = 'other_user') + self.question = self.post_question() + self.min_rep = askbot_settings.MIN_REP_TO_DELETE_OTHERS_POSTS + + def post_answer(self, user = None): + if user is None: + user = self.user + self.answer = super( + DeleteAnswerPermissionAssertionTests, + self + ).post_answer( + question = self.question, + user = user + ) + + def assert_can_delete(self): + self.user.assert_can_delete_answer(self.answer) + + def assert_cannot_delete(self): + self.assertRaises( + exceptions.PermissionDenied, + self.user.assert_can_delete_answer, + answer = self.answer + ) + + def test_low_rep_user_cannot_delete(self): + self.post_answer(user = self.other_user) + assert(self.user.reputation < self.min_rep) + self.assert_cannot_delete() + + def test_high_rep_user_can_delete(self): + self.post_answer(user = self.other_user) + self.user.reputation = self.min_rep + self.assert_can_delete() + + def test_low_rep_owner_can_delete(self): + self.post_answer() + assert(self.user.reputation < self.min_rep) + self.assert_can_delete() + + def test_suspended_owner_can_delete(self): + self.post_answer() + assert(self.user.reputation < self.min_rep) + self.user.set_status('s') + self.assert_can_delete() + + def test_blocked_owner_cannot_delete(self): + self.post_answer() + assert(self.user.reputation < self.min_rep) + self.user.set_status('b') + self.assert_cannot_delete() + + def test_blocked_user_cannot_delete(self): + self.post_answer(user = self.other_user) + self.user.set_status('b') + self.assert_cannot_delete() + + def test_high_rep_blocked_owner_cannot_delete(self): + self.post_answer() + self.user.set_status('b') + self.user.reputation = 100000 + self.assert_cannot_delete() + + def test_low_rep_admin_can_delete(self): + self.post_answer(user = self.other_user) + self.user.is_superuser = True + assert(self.user.reputation < self.min_rep) + self.assert_can_delete() + + def test_low_rep_moderator_can_delete(self): + self.post_answer(user = self.other_user) + self.user.set_status('m') + assert(self.user.reputation < self.min_rep) + self.assert_can_delete() + +class DeleteQuestionPermissionAssertionTests(utils.AskbotTestCase): + """These specifically test cases where user is + owner of the question + + all other cases are the same as DeleteAnswer... + """ + + def setUp(self): + self.create_user() + self.create_user(username = 'other_user') + self.question = self.post_question() + + def assert_can_delete(self): + self.user.assert_can_delete_question( + question = self.question + ) + + def assert_cannot_delete(self): + self.assertRaises( + exceptions.PermissionDenied, + self.user.assert_can_delete_question, + question = self.question + ) + + def upvote_answer(self, answer = None, user = None): + if user is None: + user = self.user + user.reputation = askbot_settings.MIN_REP_TO_VOTE_UP + user.upvote(answer) + + def test_owner_can_delete_question_with_nonvoted_answer_by_other(self): + self.post_answer( + user = self.other_user, + question = self.question + ) + self.assert_can_delete() + + def test_owner_can_delete_question_with_upvoted_answer_posted_by_self(self): + answer = self.post_answer( + user = self.user, + question = self.question + ) + self.upvote_answer( + answer = answer, + user = self.other_user + ) + self.assert_can_delete() + + def test_owner_cannot_delete_question_with_upvoted_answer_posted_by_other(self): + answer = self.post_answer( + user = self.other_user, + question = self.question + ) + self.upvote_answer( + answer = answer, + user = self.user + ) + self.assert_cannot_delete() + + def test_owner_can_delete_question_without_answers(self): + self.assert_can_delete() + + def test_moderator_can_delete_question_with_upvoted_answer_by_other(self): + self.user.set_status('m') + answer = self.post_answer( + user = self.other_user, + question = self.question + ) + self.user.upvote(answer) + self.assert_can_delete() + class CloseQuestionPermissionAssertionTests(utils.AskbotTestCase): diff --git a/askbot/views/commands.py b/askbot/views/commands.py index 81a93be7..d3b6dc15 100644 --- a/askbot/views/commands.py +++ b/askbot/views/commands.py @@ -195,17 +195,28 @@ def vote(request, id): elif vote_type in ['7', '8']: #flag question or answer if vote_type == '7': - post_id = id post = get_object_or_404(Question, id=id) if vote_type == '8': - post_id = request.POST.get('postId') - post = get_object_or_404(Answer, id=post_id) + id = request.POST.get('postId') + post = get_object_or_404(Answer, id=id) request.user.flag_post(post) response_data['count'] = post.offensive_flag_count response_data['success'] = 1 + elif vote_type in ['9', '10']: + #delete question or answer + post = get_object_or_404(Question, id = id) + if vote_type == '10': + id = request.POST.get('postId') + post = get_object_or_404(Answer, id = id) + + if post.deleted == True: + request.user.restore_post(post = post) + else: + request.user.delete_post(post = post) + elif request.is_ajax() and request.method == 'POST': if not request.user.is_authenticated(): @@ -225,25 +236,6 @@ def vote(request, id): if fave == False: response_data['status'] = 1 - elif vote_type in ['9', '10']: - #delete question or answer - post = question - post_id = id - if vote_type == '10': - post_id = request.POST.get('postId') - post = get_object_or_404(Answer, id=post_id) - - if post.deleted == True: - logging.debug('debug restoring post in view') - auth.onDeleteCanceled(post, request.user) - response_data['status'] = 1 - else: - try: - request.user.delete_post(post = post) - except exceptions.PermissionDenied, e: - response_data['allowed'] = -2 - request.user.message_set.create(message = str(e)) - elif vote_type == '11':#subscribe q updates user = request.user if user.is_authenticated(): |