diff options
-rw-r--r-- | askbot/auth.py | 35 | ||||
-rw-r--r-- | askbot/exceptions.py | 14 | ||||
-rw-r--r-- | askbot/locale/en/LC_MESSAGES/django.mo | bin | 24826 -> 25398 bytes | |||
-rw-r--r-- | askbot/locale/en/LC_MESSAGES/django.po | 18 | ||||
-rw-r--r-- | askbot/models/__init__.py | 87 | ||||
-rw-r--r-- | askbot/models/signals.py | 2 | ||||
-rwxr-xr-x | askbot/skins/default/media/js/com.cnprog.post.js | 123 | ||||
-rwxr-xr-x | askbot/skins/default/media/style/style.css | 33 | ||||
-rw-r--r-- | askbot/skins/default/templates/question.html | 32 | ||||
-rw-r--r-- | askbot/templatetags/extra_filters.py | 65 | ||||
-rw-r--r-- | askbot/tests/__init__.py | 1 | ||||
-rw-r--r-- | askbot/tests/db_api_tests.py | 29 | ||||
-rw-r--r-- | askbot/tests/permission_assertion_tests.py | 277 | ||||
-rw-r--r-- | askbot/tests/utils.py | 113 | ||||
-rw-r--r-- | askbot/utils/decorators.py | 14 | ||||
-rw-r--r-- | askbot/views/commands.py | 59 | ||||
-rw-r--r-- | askbot/views/writers.py | 100 |
17 files changed, 768 insertions, 234 deletions
diff --git a/askbot/auth.py b/askbot/auth.py index 6a92b005..1ab24f52 100644 --- a/askbot/auth.py +++ b/askbot/auth.py @@ -18,26 +18,6 @@ import logging from askbot.conf import settings as askbot_settings -def can_flag_offensive(user): - """Determines if a User can flag Questions and Answers as offensive.""" - return user.is_authenticated() and ( - user.reputation >= askbot_settings.MIN_REP_TO_FLAG_OFFENSIVE or - user.is_superuser) - -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: - return True - if user.reputation >= askbot_settings.MIN_REP_TO_LEAVE_COMMENTS: - return True - if user.is_superuser: - return True - if isinstance(subject, Answer): - if subject.question.author.id == user.id: - return True - return False - def can_retag_questions(user): """Determines if a User can retag Questions.""" if user.is_authenticated(): @@ -65,18 +45,11 @@ def can_edit_post(user, post): return True return False -def can_delete_comment(user, comment): - """Determines if a User can delete the given Comment.""" - return user.is_authenticated() and ( - user.id == comment.user_id or - user.reputation >= askbot_settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS or - user.is_superuser) - def can_view_offensive_flags(user): """Determines if a User can view offensive flag counts.""" return user.is_authenticated() and ( user.reputation >= askbot_settings.MIN_REP_TO_VIEW_OFFENSIVE_FLAGS or - user.is_superuser) + user.is_superuser or user.is_moderator()) def can_close_question(user, question): """Determines if a User can close the given Question.""" @@ -150,7 +123,7 @@ def onFlaggedItem(item, post, user, timestamp=None): post.author.reputation = calculate_reputation( post.author.reputation, - askbot_settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE + askbot_settings.REP_LOSS_FOR_RECEIVING_FLAG ) post.author.save() @@ -160,7 +133,7 @@ def onFlaggedItem(item, post, user, timestamp=None): reputation = Repute( user=post.author, - negative=askbot_settings.REP_LOSS_FOR_RECEIVING_DOWNVOTE, + negative=askbot_settings.REP_LOSS_FOR_RECEIVING_FLAG, question=question, reputed_at=timestamp, reputation_type=-4, reputation=post.author.reputation @@ -212,7 +185,7 @@ def onFlaggedItem(item, post, user, timestamp=None): #post.deleted_at = timestamp #post.deleted_by = Admin post.save() - signals.mark_offensive.send( + signals.flag_offensive.send( sender=post.__class__, instance=post, mark_by=user diff --git a/askbot/exceptions.py b/askbot/exceptions.py new file mode 100644 index 00000000..0e7cb712 --- /dev/null +++ b/askbot/exceptions.py @@ -0,0 +1,14 @@ +from django.core import exceptions + +class InsufficientReputation(exceptions.PermissionDenied): + """exception class to indicate that permission + was denied due to insufficient reputation + """ + pass + +class DuplicateCommand(exceptions.PermissionDenied): + """exception class to indicate that something + that can happen only once was attempted for the second time + """ + pass + diff --git a/askbot/locale/en/LC_MESSAGES/django.mo b/askbot/locale/en/LC_MESSAGES/django.mo Binary files differindex 21a76706..25f26273 100644 --- a/askbot/locale/en/LC_MESSAGES/django.mo +++ b/askbot/locale/en/LC_MESSAGES/django.mo diff --git a/askbot/locale/en/LC_MESSAGES/django.po b/askbot/locale/en/LC_MESSAGES/django.po index 4442a2e6..94b64b78 100644 --- a/askbot/locale/en/LC_MESSAGES/django.po +++ b/askbot/locale/en/LC_MESSAGES/django.po @@ -27,6 +27,24 @@ msgstr "" msgid "anonymous users cannot vote" msgstr "sorry, anonymous users cannot vote " +msgid "cannot flag message as offensive twice" +msgstr "You have flagged this question before and " +"cannot do it more than once" + +msgid "blocked users cannot flag posts" +msgstr "Sorry, since your account is blocked " +"you cannot flag posts as offensive" + +#, python-format +msgid "need > %(min_rep)s points to flag spam" +msgstr "Sorry, to flag posts as offensive a minimum " +"reputation of %(min_rep)s is required" + +#, python-format +msgid "%(max_flags_per_day)s exceeded" +msgstr "Sorry, you have exhausted the maximum number " +"of %(max_flags_per_day)s offensive flags per day." + msgid "blocked users cannot post" msgstr "Sorry, your account appears to be blocked and you " "cannot make new posts until this issue is resolved. " diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index e75593b5..a59448c1 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -14,7 +14,8 @@ from django.utils.safestring import mark_safe from django.db import models from django.conf import settings as django_settings from django.contrib.contenttypes.models import ContentType -from django.core import exceptions +from django.core import exceptions as django_exceptions +from askbot import exceptions as askbot_exceptions from askbot import const from askbot.conf import settings as askbot_settings from askbot.models.question import Question, QuestionRevision @@ -33,12 +34,6 @@ from askbot.startup_tests import run_startup_tests run_startup_tests() -class InsufficientReputation(exceptions.PermissionDenied): - """exception class to indicate that permission - was denied due to insufficient reputation - """ - pass - User.add_to_class( 'status', models.CharField( @@ -126,10 +121,10 @@ def _assert_user_can( return elif low_rep_error_message and user.reputation < min_rep_setting: error_message = low_rep_error_message % {'min_rep': min_rep_setting} - raise InsufficientReputation(error_message) + raise askbot_exceptions.InsufficientReputation(error_message) else: return - raise exceptions.PermissionDenied(error_message) + raise django_exceptions.PermissionDenied(error_message) def user_assert_can_vote_for_post( @@ -145,7 +140,7 @@ def user_assert_can_vote_for_post( """ if self == post.author: - raise exceptions.PermissionDenied('cannot vote for own posts') + raise django_exceptions.PermissionDenied('cannot vote for own posts') blocked_error_message = _( 'Sorry your account appears to be blocked ' + @@ -243,7 +238,7 @@ def user_assert_can_post_comment(self, parent_post = None): min_rep_setting = askbot_settings.MIN_REP_TO_LEAVE_COMMENTS, low_rep_error_message = low_rep_error_message, ) - except InsufficientReputation, e: + except askbot_exceptions.InsufficientReputation, e: if isinstance(parent_post, Answer): if self == parent_post.question.author: return @@ -344,21 +339,21 @@ def user_assert_can_close_question(self, question = None): ) -def user_assert_can_flag_offensive(self): +def user_assert_can_flag_offensive(self, post = None): - blocked_error_message = _( - 'Sorry, since your account is blocked ' + \ - 'you cannot flag posts as offensive' - ) - suspended_error_message = _( - 'Sorry, since your account is suspended ' + \ - 'you cannot flag posts as offensive' - ) - low_rep_error_message = _( - 'Sorry, to flag posts as offensive a minimum ' + \ - 'reputation of %(min_rep)s is required' - ) % \ - {'min_rep': askbot_settings.MIN_REP_TO_FLAG_OFFENSIVE} + assert(post is not None) + + double_flagging_error_message = _('cannot flag message as offensive twice') + + if post.flagged_items.filter(user = self).count() > 0: + raise askbot_exceptions.DuplicateCommand(double_flagging_error_message) + + blocked_error_message = _('blocked users cannot flag posts') + + suspended_error_message = _('suspended users cannot flag posts') + + low_rep_error_message = _('need > %(min_rep)s points to flag spam') % \ + {'min_rep': askbot_settings.MIN_REP_TO_FLAG_OFFENSIVE} min_rep_setting = askbot_settings.MIN_REP_TO_FLAG_OFFENSIVE _assert_user_can( @@ -369,6 +364,21 @@ def user_assert_can_flag_offensive(self): low_rep_error_message = low_rep_error_message, min_rep_setting = min_rep_setting ) + #one extra assertion + if self.is_administrator() or self.is_moderator(): + return + else: + flag_count_today = FlaggedItem.objects.get_flagged_items_count_today( + self + ) + if flag_count_today >= askbot_settings.MAX_FLAGS_PER_USER_PER_DAY: + flags_exceeded_error_message = _( + '%(max_flags_per_day)s exceeded' + ) % { + 'max_flags_per_day': \ + askbot_settings.MAX_FLAGS_PER_USER_PER_DAY + } + raise django_exceptions.PermissionDenied(flags_exceeded_error_message) def user_assert_can_retag_questions(self): @@ -432,7 +442,7 @@ def user_assert_can_revoke_old_vote(self, vote): """ if (datetime.datetime.now().day - vote.voted_at.day) \ >= askbot_settings.MAX_DAYS_TO_CANCEL_VOTE: - raise exceptions.PermissionDenied(_('cannot revoke old vote')) + raise django_exceptions.PermissionDenied(_('cannot revoke old vote')) def user_get_unused_votes_today(self): """returns number of votes that are @@ -757,7 +767,6 @@ def user_get_status_display(self, soft = False): elif soft == True: return _('Registered User') elif self.is_watched(): - print 'watched' return _('Watched User') elif self.is_approved(): return _('Approved User') @@ -936,18 +945,18 @@ def accept_answer(self, answer, timestamp=None, cancel=False): else: auth.onAnswerAccept(answer, self, timestamp=timestamp) +@auto_now_timestamp 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 = user, - content_object = post, - flagged_at = timestamp, - ) - auth.onFlaggedItem(flag, post, user, timestamp=timestamp) + + user.assert_can_flag_offensive(post = post) + flag = FlaggedItem( + user = user, + content_object = post, + flagged_at = timestamp, + ) + auth.onFlaggedItem(flag, post, user, timestamp=timestamp) def user_increment_response_count(user): """increment response counter for user @@ -1316,7 +1325,7 @@ def record_delete_question(instance, delete_by, **kwargs): #no need to set receiving user here activity.save() -def record_mark_offensive(instance, mark_by, **kwargs): +def record_flag_offensive(instance, mark_by, **kwargs): activity = Activity( user=mark_by, active_at=datetime.datetime.now(), @@ -1415,8 +1424,8 @@ django_signals.post_delete.connect(record_cancel_vote, sender=Vote) #change this to real m2m_changed with Django1.2 signals.delete_question_or_answer.connect(record_delete_question, sender=Question) signals.delete_question_or_answer.connect(record_delete_question, sender=Answer) -signals.mark_offensive.connect(record_mark_offensive, sender=Question) -signals.mark_offensive.connect(record_mark_offensive, sender=Answer) +signals.flag_offensive.connect(record_flag_offensive, sender=Question) +signals.flag_offensive.connect(record_flag_offensive, sender=Answer) signals.tags_updated.connect(record_update_tags, sender=Question) signals.user_updated.connect(record_user_full_updated, sender=User) signals.user_logged_in.connect(post_stored_anonymous_content) diff --git a/askbot/models/signals.py b/askbot/models/signals.py index 1c5324c3..825f72e7 100644 --- a/askbot/models/signals.py +++ b/askbot/models/signals.py @@ -9,7 +9,7 @@ edit_question_or_answer = django.dispatch.Signal( delete_question_or_answer = django.dispatch.Signal( providing_args=['instance', 'deleted_by'] ) -mark_offensive = django.dispatch.Signal(providing_args=['instance', 'mark_by']) +flag_offensive = django.dispatch.Signal(providing_args=['instance', 'mark_by']) user_updated = django.dispatch.Signal(providing_args=['instance', 'updated_by']) #todo: move this to authentication app user_logged_in = django.dispatch.Signal(providing_args=['session']) diff --git a/askbot/skins/default/media/js/com.cnprog.post.js b/askbot/skins/default/media/js/com.cnprog.post.js index 90956d9e..b82be4ee 100755 --- a/askbot/skins/default/media/js/com.cnprog.post.js +++ b/askbot/skins/default/media/js/com.cnprog.post.js @@ -61,17 +61,14 @@ var Vote = function(){ var pleaseSeeFAQ = $.i18n._('please see') + "<a href='" + scriptUrl + $.i18n._("faq/") + "'>faq</a>"; - var favoriteAnonymousMessage = $.i18n._('anonymous users cannot select favorite questions'); + var favoriteAnonymousMessage = $.i18n._('anonymous users cannot select favorite questions') + pleaseLogin; var voteAnonymousMessage = $.i18n._('anonymous users cannot vote') + pleaseLogin; //there were a couple of more messages... var voteDenyCancelMessage = $.i18n._('cannot revoke old vote') + pleaseSeeFAQ; var offensiveConfirmation = $.i18n._('please confirm offensive'); var offensiveAnonymousMessage = $.i18n._('anonymous users cannot flag offensive posts') + pleaseLogin; - var offensiveTwiceMessage = $.i18n._('cannot flag message as offensive twice') + pleaseSeeFAQ; - var offensiveNoFlagsLeftMessage = $.i18n._('flag offensive cap exhausted') + pleaseSeeFAQ; - var offensiveNoPermissionMessage = $.i18n._('need >15 points to report spam') + pleaseSeeFAQ; var removeConfirmation = $.i18n._('confirm delete'); - var removeAnonymousMessage = $.i18n._('anonymous users cannot delete/undelete'); + var removeAnonymousMessage = $.i18n._('anonymous users cannot delete/undelete') + pleaseLogin; var recoveredMessage = $.i18n._('post recovered'); var deletedMessage = $.i18n._('post deleted'); @@ -280,7 +277,15 @@ var Vote = function(){ var callback_favorite = function(object, voteType, data){ if(data.allowed == "0" && data.success == "0"){ - showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId)); + showMessage( + object, + favoriteAnonymousMessage.replace( + '{{QuestionID}}', + questionId).replace( + '{{questionSlug}}', + '' + ) + ); } else if(data.status == "1"){ object.attr("src", mediaUrl("media/images/vote-favorite-off.png")); @@ -335,27 +340,33 @@ var Vote = function(){ }; var callback_offensive = function(object, voteType, data){ - object = $(object); - if (data.allowed == "0" && data.success == "0"){ - showMessage(object, offensiveAnonymousMessage.replace("{{QuestionID}}", questionId)); - } - else if (data.allowed == "-3"){ - showMessage(object, offensiveNoFlagsLeftMessage); - } - else if (data.allowed == "-2"){ - showMessage(object, offensiveNoPermissionMessage); - } - else if (data.status == "1"){ - showMessage(object, offensiveTwiceMessage); - } - else if (data.success == "1"){ + //todo: transfer proper translations of these from i18n.js + //to django.po files + //_('anonymous users cannot flag offensive posts') + pleaseLogin; + //_('flag offensive cap exhausted') + pleaseSeeFAQ; + //_('need >15 points to report spam') + pleaseSeeFAQ; + //_('cannot flag message as offensive twice') + pleaseSeeFAQ; + if (data.success == "1"){ $(object).children('span[class=darkred]').text("("+ data.count +")"); } + else { + object = $(object); + showMessage(object, data.message) + } }; var callback_remove = function(object, voteType, data){ if (data.allowed == "0" && data.success == "0"){ - showMessage(object, removeAnonymousMessage.replace("{{QuestionID}}", questionId)); + showMessage( + object, + removeAnonymousMessage.replace( + "{{QuestionID}}", + questionId + ).replace( + '{{questionSlug}}', + '' + ) + ); } else if (data.success == "1"){ if (voteType == VoteType.removeQuestion){ @@ -393,7 +404,16 @@ var Vote = function(){ //mark question as favorite favorite: function(object){ if (!currentUserId || currentUserId.toUpperCase() == "NONE"){ - showMessage(object, favoriteAnonymousMessage.replace("{{QuestionID}}", questionId)); + showMessage( + object, + favoriteAnonymousMessage.replace( + "{{QuestionID}}", + questionId + ).replace( + '{{questionSlug}}', + questionSlug + ) + ); return false; } submit(object, VoteType.favorite, callback_favorite); @@ -401,7 +421,16 @@ var Vote = function(){ vote: function(object, voteType){ if (!currentUserId || currentUserId.toUpperCase() == "NONE"){ - showMessage($(object), voteAnonymousMessage); + showMessage( + $(object), + voteAnonymousMessage.replace( + "{{QuestionID}}", + questionId + ).replace( + '{{questionSlug}}', + questionSlug + ) + ); } // up and downvote processor if (voteType == VoteType.answerUpVote){ @@ -416,7 +445,16 @@ var Vote = function(){ //flag offensive offensive: function(object, voteType){ if (!currentUserId || currentUserId.toUpperCase() == "NONE"){ - showMessage($(object), offensiveAnonymousMessage.replace("{{QuestionID}}", questionId)); + showMessage( + $(object), + offensiveAnonymousMessage.replace( + "{{QuestionID}}", + questionId + ).replace( + '{{questionSlug}}', + questionSlug + ) + ); return false; } if (confirm(offensiveConfirmation)){ @@ -427,7 +465,16 @@ var Vote = function(){ //delete question or answer (comments are deleted separately) remove: function(object, voteType){ if (!currentUserId || currentUserId.toUpperCase() == "NONE"){ - showMessage($(object), removeAnonymousMessage.replace("{{QuestionID}}", questionId)); + showMessage( + $(object), + removeAnonymousMessage.replace( + '{{QuestionID}}', + questionId + ).replace( + '{{questionSlug}}', + questionSlug + ) + ); return false; } bits = object.id.split('-'); @@ -590,9 +637,9 @@ function createComments(type) { commentsFactory[objectType].updateTextCounter(textarea); enableSubmitButton(formSelector); }, - error: function(res, textStatus, errorThrown) { + error: function(xhr, textStatus, errorThrown) { removeLoader(); - showMessage(formSelector, res.responseText); + showMessage($(formSelector), xhr.responseText); enableSubmitButton(formSelector); } }); @@ -610,6 +657,7 @@ function createComments(type) { var cBox = $("[id^='comments-container-" + objectType + "']"); cBox.each( function(i){ var post_id = $(this).attr('id').replace('comments-container-' + objectType + '-', ''); + $(this).children().each( function(i){ var comment_id = $(this).attr('id').replace('comment-',''); @@ -664,10 +712,19 @@ function createComments(type) { deleteComment: function(jImg, id, deleteUrl) { if (confirm($.i18n._('confirm delete comment'))) { jImg.hide(); - $.post(deleteUrl, { dataNeeded: "forIIS7" }, function(json) { - var par = jImg.parent(); - par.remove(); - }, "json"); + $.ajax({ + type: 'POST', + url: deleteUrl, + data: { dataNeeded: "forIIS7" }, + success: function(json, textStatus, xhr) { + var par = jImg.parent(); + par.remove(); + }, + error: function(xhr, textStatus, exception) { + showMessage(jImg, xhr.responseText); + }, + dataType: "json" + }); } }, @@ -676,8 +733,8 @@ function createComments(type) { var color = length > 270 ? "#f00" : length > 200 ? "#f60" : "#999"; var jSpan = $(textarea).siblings("span.text-counter"); jSpan.html($.i18n._('can write') + - (300 - length) + ' ' + - $.i18n._('characters')).css("color", color); + (300 - length) + ' ' + + $.i18n._('characters')).css("color", color); } }; } diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css index b2e74d37..44276798 100755 --- a/askbot/skins/default/media/style/style.css +++ b/askbot/skins/default/media/style/style.css @@ -1227,22 +1227,6 @@ a:hover.medal { color: #777; } -.vote-notification { - z-index: 1; - cursor: pointer; - display: none; - position: absolute; - padding: 15px; - color: White; - background-color: darkred; - text-align: center; -} - -.vote-notification a { - color: White; - text-decoration: underline; -} - .offensive-flag a { color: #777; padding: 3px; @@ -2677,3 +2661,20 @@ p.signup_p { img.gravatar { margin:2px; } + +.vote-notification { + z-index: 1; + cursor: pointer; + display: none; + position: absolute; + padding: 15px; + color: white; + background-color: darkred; + text-align: center; +} + +.vote-notification a { + color: white; + text-decoration: underline; +} + diff --git a/askbot/skins/default/templates/question.html b/askbot/skins/default/templates/question.html index abcf5585..3cb7fb9c 100644 --- a/askbot/skins/default/templates/question.html +++ b/askbot/skins/default/templates/question.html @@ -153,12 +153,12 @@ {% endif %} {% endif %} {% separator %} - {% if request.user|can_flag_offensive %} + {% if request.user|can_flag_offensive:question %} <span id="question-offensive-flag-{{ question.id }}" class="offensive-flag" title="{% trans "report as offensive (i.e containing spam, advertising, malicious text, etc.)" %}"> <a>{% trans "flag offensive" %}</a> - {% if request.user|can_view_offensive_flags and question.offensive_flag_count %} - <span class="darkred">({{ question.offensive_flag_count }})</span> + {% if request.user|can_view_offensive_flags %} + <span class="darkred">{% if question.offensive_flag_count %}({{ question.offensive_flag_count }}){% endif %}</span> {% endif %} </span> {% endif %} @@ -178,7 +178,7 @@ {{comment.html|safe}} - <a class="comment-user" href="{{comment.user.get_profile_url}}">{{comment.user}}</a> {% spaceless %} - <span class="comment-age">({% diff_date comment.added_at %})</span> + <span class="comment-age"> ({% diff_date comment.added_at %})</span> {% if request.user|can_delete_comment:comment %} <img class="delete-icon" src="{% media "/media/images/close-small.png" %}" @@ -189,14 +189,14 @@ {% endfor %} </div> <div class="post-comments" style="margin-bottom:20px"> - <input id="can-post-comments-question-{{question.id}}" type="hidden" value="{{ request.user|can_add_comments:question }}"/> - {% if request.user|can_add_comments:question or question.comment_count > 5 %} + <input id="can-post-comments-question-{{question.id}}" type="hidden" value="{{ request.user|can_post_comment:question }}"/> + {% if request.user|can_post_comment:question or question.comment_count > 5 %} <a id="comments-link-question-{{question.id}}" class="comments-link"> - {% if request.user|can_add_comments:question %} + {% if request.user|can_post_comment:question %} {% trans "add comment" %} {% endif %} {% if question.comment_count > 5 %} - {% if request.user|can_add_comments:question %}/ + {% if request.user|can_post_comment:question %}/ {% blocktrans count question.get_comments|slice:"5:"|length as counter %}see <strong>{{counter}}</strong> more{% plural %}see <strong>{{counter}}</strong> more{% endblocktrans %} {% else %} {% blocktrans count question.get_comments|slice:"5:"|length as counter %}see <strong>{{counter}}</strong> more comment{% plural %}see <strong>{{counter}}</strong> more comments @@ -290,12 +290,12 @@ <span class="action-link"><a href="{% url edit_answer answer.id %}">{% trans 'edit' %}</a></span> {% endif %} {% separator %} - {% if request.user|can_flag_offensive %} + {% if request.user|can_flag_offensive:answer %} <span id="answer-offensive-flag-{{ answer.id }}" class="offensive-flag" title="{% trans "report as offensive (i.e containing spam, advertising, malicious text, etc.)" %}"> <a>{% trans "flag offensive" %}</a> - {% if request.user|can_view_offensive_flags and answer.offensive_flag_count %} - <span class="darkred">({{ answer.offensive_flag_count }})</span> + {% if request.user|can_view_offensive_flags %} + <span class="darkred">{% if answer.offensive_flag_count %}({{ answer.offensive_flag_count }}){% endif %}</span> {% endif %} </span> {% endif %} @@ -320,7 +320,7 @@ {{comment.html|safe}} - <a class="comment-user" href="{{comment.user.get_profile_url}}">{{comment.user}}</a> {% spaceless %} - <span class="comment-age">({% diff_date comment.added_at %})</span> + <span class="comment-age"> ({% diff_date comment.added_at %})</span> {% if request.user|can_delete_comment:comment %} <img class="delete-icon" src="{% media "/media/images/close-small.png" %}" @@ -331,14 +331,14 @@ {% endfor %} </div> <div class="post-comments" style="margin-bottom:20px"> - <input id="can-post-comments-answer-{{answer.id}}" type="hidden" value="{{ request.user|can_add_comments:answer}}"/> - {% if request.user|can_add_comments:answer or answer.comment_count > 5 %} + <input id="can-post-comments-answer-{{answer.id}}" type="hidden" value="{{ request.user|can_post_comment:answer}}"/> + {% if request.user|can_post_comment:answer or answer.comment_count > 5 %} <a id="comments-link-answer-{{answer.id}}" class="comments-link"> - {% if request.user|can_add_comments:answer %} + {% if request.user|can_post_comment:answer %} {% trans "add comment" %} {% endif %} {% if answer.comment_count > 5 %} - {% if request.user|can_add_comments:answer %}/ + {% if request.user|can_post_comment:answer %}/ {% blocktrans count answer.get_comments|slice:"5:"|length as counter %}see <strong>{{counter}}</strong> more{% plural %}see <strong>{{counter}}</strong> more{% endblocktrans %} {% else %} {% blocktrans count answer.get_comments|slice:"5:"|length as counter %}see <strong>{{counter}}</strong> more comment{% plural %} see <strong>{{counter}}</strong> more comments{% endblocktrans %} diff --git a/askbot/templatetags/extra_filters.py b/askbot/templatetags/extra_filters.py index a8884895..383c8f7c 100644 --- a/askbot/templatetags/extra_filters.py +++ b/askbot/templatetags/extra_filters.py @@ -1,5 +1,6 @@ from django import template -from django.core import exceptions +from django.core import exceptions as django_exceptions +from askbot import exceptions as askbot_exceptions from askbot import auth from askbot import models from askbot.deps.grapefruit import Color @@ -13,19 +14,57 @@ register = template.Library() def collapse(input): return ' '.join(input.split()) +def make_test_from_permission_assertion( + assertion_name = None, + allowed_exception = None + ): + """a decorator-like function that will create a True/False test from + permission assertion + """ + def test_function(user, post): + if user.is_anonymous(): + return False + + assertion = getattr(user, assertion_name) + if allowed_exception: + try: + assertion(post) + return True + except allowed_exception: + return True + except django_exceptions.PermissionDenied: + return False + else: + try: + assertion(post) + return True + except django_exceptions.PermissionDenied: + return False + + return test_function + + @register.filter def can_moderate_user(user, other_user): if user.is_authenticated() and user.can_moderate_user(other_user): return True return False -@register.filter -def can_flag_offensive(user): - return auth.can_flag_offensive(user) +can_flag_offensive = make_test_from_permission_assertion( + assertion_name = 'assert_can_flag_offensive', + allowed_exception = askbot_exceptions.DuplicateCommand + ) +register.filter('can_flag_offensive', can_flag_offensive) -@register.filter -def can_add_comments(user, subject): - return auth.can_add_comments(user, subject) +can_post_comment = make_test_from_permission_assertion( + assertion_name = 'assert_can_post_comment' + ) +register.filter('can_post_comment', can_post_comment) + +can_delete_comment = make_test_from_permission_assertion( + assertion_name = 'assert_can_delete_comment' + ) +register.filter('can_delete_comment', can_delete_comment) @register.filter def can_retag_questions(user): @@ -36,16 +75,6 @@ def can_edit_post(user, post): return auth.can_edit_post(user, post) @register.filter -def can_delete_comment(user, comment): - if user.is_anonymous(): - return False - try: - user.assert_can_delete_comment(comment) - return True - except exceptions.PermissionDenied: - return False - -@register.filter def can_view_offensive_flags(user): return auth.can_view_offensive_flags(user) @@ -78,7 +107,7 @@ def can_delete_post(user, post): return True else: return False - except exceptions.PermissionDenied: + except django_exceptions.PermissionDenied: return False @register.filter diff --git a/askbot/tests/__init__.py b/askbot/tests/__init__.py index bc1b9431..329a5271 100644 --- a/askbot/tests/__init__.py +++ b/askbot/tests/__init__.py @@ -2,3 +2,4 @@ from askbot.tests.email_alert_tests import * from askbot.tests.on_screen_notification_tests import * from askbot.tests.page_load_tests import * from askbot.tests.permission_assertion_tests import * +from askbot.tests.db_api_tests import * diff --git a/askbot/tests/db_api_tests.py b/askbot/tests/db_api_tests.py new file mode 100644 index 00000000..8aa7fa78 --- /dev/null +++ b/askbot/tests/db_api_tests.py @@ -0,0 +1,29 @@ +"""Tests database api - the basic data entry +functions that happen on behalf of users + +e.g. ``some_user.do_something(...)`` +""" +from askbot.tests.utils import AskbotTestCase + +class DBApiTests(AskbotTestCase): + + def test_flag_question(self): + self.create_user() + question = self.post_question() + self.user.set_status('m') + self.user.flag_post(question) + self.assertEquals( + len(self.user.flaggeditems.all()), + 1 + ) + + def test_flag_answer(self): + self.create_user() + question = self.post_question() + answer = self.post_answer(question = question) + self.user.set_status('m') + self.user.flag_post(answer) + self.assertEquals( + len(self.user.flaggeditems.all()), + 1 + ) diff --git a/askbot/tests/permission_assertion_tests.py b/askbot/tests/permission_assertion_tests.py index dd84a85b..dfef0ca9 100644 --- a/askbot/tests/permission_assertion_tests.py +++ b/askbot/tests/permission_assertion_tests.py @@ -3,6 +3,7 @@ from django.core import exceptions from askbot.tests import utils from askbot.conf import settings as askbot_settings from askbot import models +from askbot.templatetags import extra_filters as template_filters class PermissionAssertionTestCase(TestCase): """base TestCase class for permission @@ -44,6 +45,180 @@ class PermissionAssertionTestCase(TestCase): body_text = 'test answer' ) +class FlagOffensivePermissionAssertionTests(PermissionAssertionTestCase): + + def extraSetUp(self): + self.min_rep = askbot_settings.MIN_REP_TO_FLAG_OFFENSIVE + self.question = self.post_question() + self.answer = self.post_answer(question = self.question) + + def assert_user_cannot_flag(self): + self.assertRaises( + exceptions.PermissionDenied, + self.user.flag_post, + post = self.question + ) + self.assertFalse( + template_filters.can_flag_offensive( + self.user, + self.question + ) + ) + self.assertRaises( + exceptions.PermissionDenied, + self.user.flag_post, + post = self.answer + ) + self.assertFalse( + template_filters.can_flag_offensive( + self.user, + self.answer + ) + ) + + def assert_user_can_flag(self): + self.user.flag_post(post = self.question) + self.assertTrue( + template_filters.can_flag_offensive( + self.user, + self.question + ) + ) + self.user.flag_post(post = self.answer) + self.assertTrue( + template_filters.can_flag_offensive( + self.user, + self.answer + ) + ) + + def setup_high_rep(self): + #there is a catch - assert_user_can_flag + #flags twice and each time user reputation + #suffers a hit, so test may actually fail + #set amply high reputation + extra_rep = -100 * askbot_settings.REP_LOSS_FOR_RECEIVING_FLAG + #NB: REP_LOSS is negative + self.user.reputation = self.min_rep + extra_rep + self.user.save() + + def test_high_rep_user_cannot_exceed_max_flags_per_day(self): + max_flags = askbot_settings.MAX_FLAGS_PER_USER_PER_DAY + other_user = self.create_other_user() + other_user.reputation = self.min_rep + for i in range(max_flags): + question = self.post_question() + other_user.flag_post(question) + question = self.post_question() + self.assertRaises( + exceptions.PermissionDenied, + other_user.flag_post, + question + ) + + def test_admin_has_no_limit_for_flags_per_day(self): + max_flags = askbot_settings.MAX_FLAGS_PER_USER_PER_DAY + other_user = self.create_other_user() + other_user.is_superuser = True + for i in range(max_flags + 1): + question = self.post_question() + other_user.flag_post(question) + + def test_moderator_has_no_limit_for_flags_per_day(self): + max_flags = askbot_settings.MAX_FLAGS_PER_USER_PER_DAY + other_user = self.create_other_user() + other_user.set_status('m') + for i in range(max_flags + 1): + question = self.post_question() + other_user.flag_post(question) + + def test_low_rep_user_cannot_flag(self): + assert(self.user.reputation < self.min_rep) + self.assert_user_cannot_flag() + + def test_high_rep_blocked_or_suspended_user_cannot_flag(self): + self.setup_high_rep() + self.user.set_status('b') + self.assert_user_cannot_flag() + self.user.set_status('s') + self.assert_user_cannot_flag() + + def test_high_rep_user_can_flag(self): + self.setup_high_rep() + self.assert_user_can_flag() + + def test_low_rep_moderator_can_flag(self): + assert(self.user.reputation < self.min_rep) + self.user.set_status('m') + self.assert_user_can_flag() + + def low_rep_administrator_can_flag(self): + assert(self.user.reputation < self.min_rep) + self.user.is_superuser = True + self.assert_user_can_flag() + + def test_superuser_cannot_flag_question_twice(self): + self.user.is_superuser = True + self.user.flag_post(post = self.question) + self.assertRaises( + exceptions.PermissionDenied, + self.user.flag_post, + post = self.question + ) + #here is a deviation - the link will still be shown + #in templates + self.assertTrue( + template_filters.can_flag_offensive( + self.user, + self.question + ) + ) + + def test_superuser_cannot_flag_answer_twice(self): + self.user.is_superuser = True + self.user.flag_post(post = self.answer) + self.assertRaises( + exceptions.PermissionDenied, + self.user.flag_post, + post = self.answer + ) + self.assertTrue( + template_filters.can_flag_offensive( + self.user, + self.answer + ) + ) + + def test_high_rep_user_cannot_flag_question_twice(self): + self.user.reputation = self.min_rep + self.user.flag_post(post = self.question) + self.assertRaises( + exceptions.PermissionDenied, + self.user.flag_post, + post = self.question + ) + self.assertTrue( + template_filters.can_flag_offensive( + self.user, + self.question + ) + ) + + def test_high_rep_user_cannot_flag_answer_twice(self): + self.user.reputation = self.min_rep + self.user.flag_post(post = self.answer) + self.assertRaises( + exceptions.PermissionDenied, + self.user.flag_post, + post = self.answer + ) + self.assertTrue( + template_filters.can_flag_offensive( + self.user, + self.answer + ) + ) + class CommentPermissionAssertionTests(PermissionAssertionTestCase): @@ -61,6 +236,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): parent_post = question, body_text = 'test comment' ) + self.assertFalse( + template_filters.can_post_comment( + self.user, + question + ) + ) def test_blocked_user_cannot_comment_own_answer(self): question = self.post_question() @@ -74,6 +255,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): parent_post = answer, body_text = 'test comment' ) + self.assertFalse( + template_filters.can_post_comment( + self.user, + answer + ) + ) def test_blocked_user_cannot_delete_own_comment(self): question = self.post_question() @@ -87,6 +274,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): self.user.delete_post, post = comment ) + self.assertFalse( + template_filters.can_delete_comment( + self.user, + comment + ) + ) def test_low_rep_user_cannot_delete_others_comment(self): question = self.post_question() @@ -103,6 +296,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): self.other_user.delete_post, post = comment ) + self.assertFalse( + template_filters.can_delete_comment( + self.other_user, + comment + ) + ) def test_high_rep_user_can_delete_comment(self): question = self.post_question() @@ -114,6 +313,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): askbot_settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS self.other_user.delete_comment(comment) + self.assertTrue( + template_filters.can_delete_comment( + self.other_user, + comment + ) + ) def test_low_rep_user_can_delete_own_comment(self): question = self.post_question() @@ -130,6 +335,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): askbot_settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS ) self.user.delete_comment(comment) + self.assertTrue( + template_filters.can_delete_comment( + self.user, + comment + ) + ) def test_moderator_can_delete_comment(self): question = self.post_question() @@ -139,6 +350,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): ) self.other_user.set_status('m') self.other_user.delete_comment(comment) + self.assertTrue( + template_filters.can_delete_comment( + self.other_user, + comment + ) + ) def test_admin_can_delete_comment(self): question = self.post_question() @@ -148,6 +365,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): ) self.other_user.is_superuser = True self.other_user.delete_comment(comment) + self.assertTrue( + template_filters.can_delete_comment( + self.other_user, + comment + ) + ) def test_high_rep_suspended_user_cannot_delete_others_comment(self): question = self.post_question() @@ -163,6 +386,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): self.other_user.delete_post, post = comment ) + self.assertFalse( + template_filters.can_delete_comment( + self.other_user, + comment + ) + ) def test_suspended_user_can_delete_own_comment(self): question = self.post_question() @@ -172,6 +401,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): ) self.user.set_status('s') self.user.delete_comment(comment) + self.assertTrue( + template_filters.can_delete_comment( + self.user, + comment + ) + ) def test_low_rep_user_cannot_comment_others(self): question = self.post_question( @@ -184,6 +419,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): parent_post = question, body_text = 'test comment' ) + self.assertFalse( + template_filters.can_post_comment( + self.user, + question + ) + ) def test_low_rep_user_can_comment_others_answer_to_own_question(self): question = self.post_question() @@ -197,6 +438,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): body_text = 'test comment' ) self.assertTrue(isinstance(comment, models.Comment)) + self.assertTrue( + template_filters.can_post_comment( + self.user, + answer + ) + ) def test_high_rep_user_can_comment(self): question = self.post_question( @@ -208,6 +455,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): body_text = 'test comment' ) self.assertTrue(isinstance(comment, models.Comment)) + self.assertTrue( + template_filters.can_post_comment( + self.user, + question + ) + ) def test_suspended_user_cannot_comment_others_question(self): question = self.post_question(author = self.other_user) @@ -218,6 +471,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): parent_post = question, body_text = 'test comment' ) + self.assertFalse( + template_filters.can_post_comment( + self.user, + question + ) + ) def test_suspended_user_can_comment_own_question(self): question = self.post_question() @@ -227,6 +486,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): body_text = 'test comment' ) self.assertTrue(isinstance(comment, models.Comment)) + self.assertTrue( + template_filters.can_post_comment( + self.user, + question + ) + ) def test_low_rep_admin_can_comment_others_question(self): question = self.post_question() @@ -238,6 +503,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): body_text = 'test comment' ) self.assertTrue(isinstance(comment, models.Comment)) + self.assertTrue( + template_filters.can_post_comment( + self.other_user, + question + ) + ) def test_low_rep_moderator_can_comment_others_question(self): question = self.post_question() @@ -249,6 +520,12 @@ class CommentPermissionAssertionTests(PermissionAssertionTestCase): body_text = 'test comment' ) self.assertTrue(isinstance(comment, models.Comment)) + self.assertTrue( + template_filters.can_post_comment( + self.other_user, + question + ) + ) #def user_assert_can_post_comment(self, parent_post): #def user_assert_can_delete_comment(self, comment = None): diff --git a/askbot/tests/utils.py b/askbot/tests/utils.py index 46d8451a..b8f81717 100644 --- a/askbot/tests/utils.py +++ b/askbot/tests/utils.py @@ -1,5 +1,6 @@ """utility functions used by Askbot test cases """ +from django.test import TestCase from askbot import models def create_user( @@ -28,3 +29,115 @@ def create_user( feed.save() return user + +class AskbotTestCase(TestCase): + """adds some askbot-specific methods + to django TestCase class + """ + + def create_user( + self, + username = 'user', + email = None, + notification_schedule = None, + date_joined = None, + status = 'a' + ): + """creates user with username, etc and + makes the result accessible as + + self.<username> + + newly created user object is also returned + """ + assert(username is not None) + assert(not hasattr(self, username)) + + if email is None: + email = username + '@example.com' + + user_object = create_user( + username = username, + email = email, + notification_schedule = notification_schedule, + date_joined = date_joined, + status = status + ) + + setattr(self, username, user_object) + + return user_object + + def post_question( + self, + user = None, + title = 'test question title', + body_text = 'test question body text', + tags = 'test', + wiki = False, + follow = False, + timestamp = None + ): + """posts and returns question on behalf + of user. If user is not given, it will be self.user + + if follow is True, question is followed by the poster + """ + + if user is None: + user = self.user + + question = user.post_question( + title = title, + body_text = body_text, + tags = tags, + wiki = wiki, + timestamp = timestamp + ) + + if follow: + user.follow_question(question) + + return question + + def post_answer( + self, + user = None, + question = None, + body_text = 'test answer text', + follow = False, + wiki = False, + timestamp = None + ): + + if user is None: + user = self.user + return user.post_answer( + question = question, + body_text = body_text, + follow = follow, + wiki = wiki, + timestamp = timestamp + ) + + def post_comment( + self, + user = None, + parent_post = None, + body_text = 'test comment text', + timestamp = None + ): + """posts and returns a comment to parent post, uses + now timestamp if not given, dummy body_text + author is required + """ + if user is None: + user = self.user + + comment = user.post_comment( + parent_post = parent_post, + body_text = body_text, + timestamp = timestamp, + ) + + return comment diff --git a/askbot/utils/decorators.py b/askbot/utils/decorators.py index 8c88e426..db000d2f 100644 --- a/askbot/utils/decorators.py +++ b/askbot/utils/decorators.py @@ -6,6 +6,8 @@ import functools from django.conf import settings from django.http import HttpResponse, HttpResponseForbidden, Http404 from django.utils import simplejson +from askbot import exceptions as askbot_exceptions +from django.core import exceptions as django_exceptions def auto_now_timestamp(func): """decorator that will automatically set @@ -15,16 +17,15 @@ def auto_now_timestamp(func): """ @functools.wraps(func) def decorated_func(*arg, **kwarg): - if 'timestamp' in kwarg: - if kwarg['timestamp'] is None: - kwarg['timestamp'] = datetime.datetime.now() - return func(*arg, **kwarg) - else: - raise ValueError('timestamp argument is required') + timestamp = kwarg.get('timestamp', None) + if timestamp is None: + kwarg['timestamp'] = datetime.datetime.now() + return func(*arg, **kwarg) return decorated_func def ajax_login_required(view_func): + @functools.wraps(view_func) def wrap(request,*args,**kwargs): if request.user.is_authenticated(): return view_func(request,*args,**kwargs) @@ -35,6 +36,7 @@ def ajax_login_required(view_func): def ajax_method(view_func): + @functools.wraps(view_func) def wrap(request,*args,**kwargs): if not request.is_ajax(): raise Http404 diff --git a/askbot/views/commands.py b/askbot/views/commands.py index ca48c9bb..ba5c4bd1 100644 --- a/askbot/views/commands.py +++ b/askbot/views/commands.py @@ -192,6 +192,20 @@ def vote(request, id): post = post ) + 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) + + request.user.flag_post(post) + + response_data['count'] = post.offensive_flag_count + response_data['success'] = 1 + elif request.is_ajax() and request.method == 'POST': if not request.user.is_authenticated(): @@ -202,33 +216,7 @@ def vote(request, id): vote_type = request.POST.get('type') #accept answer - if vote_type == '00': - answer_id = request.POST.get('postId') - answer = get_object_or_404(Answer, id=answer_id) - # make sure question author is current user - if question.author == request.user: - # answer user who is also question author is not allow to accept answer - if answer.author == question.author: - response_data['success'] = 0 - response_data['allowed'] = -1 - # check if answer has been accepted already - elif answer.accepted: - auth.onAnswerAcceptCanceled(answer, request.user) - response_data['status'] = 1 - else: - # set other answers in this question not accepted first - for answer_of_question in Answer.objects.get_answers_from_question(question, request.user): - if answer_of_question != answer and answer_of_question.accepted: - auth.onAnswerAcceptCanceled(answer_of_question, request.user) - - #make sure retrieve data again after above author changes, they may have related data - answer = get_object_or_404(Answer, id=answer_id) - auth.onAnswerAccept(answer, request.user) - else: - response_data['allowed'] = 0 - response_data['success'] = 0 - # favorite - elif vote_type == '4': + if vote_type == '4': has_favorited = False fave = request.user.toggle_favorite_question(question) response_data['count'] = FavoriteQuestion.objects.filter( @@ -236,23 +224,6 @@ def vote(request, id): ).count() if fave == False: response_data['status'] = 1 - elif vote_type in ['7', '8']: - post = question - post_id = id - if vote_type == '8': - post_id = request.POST.get('postId') - post = get_object_or_404(Answer, id=post_id) - - if FlaggedItem.objects.get_flagged_items_count_today(request.user) >= askbot_settings.MAX_FLAGS_PER_USER_PER_DAY: - response_data['allowed'] = -3 - elif not auth.can_flag_offensive(request.user): - response_data['allowed'] = -2 - elif post.flagged_items.filter(user=request.user).count() > 0: - response_data['status'] = 1 - else: - item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now()) - auth.onFlaggedItem(item, post, request.user) - response_data['count'] = post.offensive_flag_count elif vote_type in ['9', '10']: #delete question or answer diff --git a/askbot/views/writers.py b/askbot/views/writers.py index 3ef14c05..65e58352 100644 --- a/askbot/views/writers.py +++ b/askbot/views/writers.py @@ -354,29 +354,35 @@ def __generate_comments_json(obj, user):#non-view generates json data for the po json_comments = [] from askbot.templatetags.extra_tags import diff_date for comment in comments: - comment_user = comment.user - delete_url = "" - if user != None and auth.can_delete_comment(user, comment): - #/posts/392845/comments/219852/delete - #todo translate this url - if isinstance(comment.content_object, models.Answer): - delete_comment_view = 'delete_answer_comment' - elif isinstance(comment.content_object, models.Question): - delete_comment_view = 'delete_question_comment' - delete_url = reverse( - delete_comment_view, - kwargs = { - 'object_id': obj.id, - 'comment_id': comment.id - } - ) - json_comments.append({"id" : comment.id, - "object_id" : obj.id, - "comment_age" : diff_date(comment.added_at), - "text" : comment.html, - "user_display_name" : comment_user.username, - "user_url" : comment_user.get_profile_url(), - "delete_url" : delete_url + + if user != None and user.is_authenticated(): + try: + user.assert_can_delete_comment(comment) + #/posts/392845/comments/219852/delete + #todo translate this url + if isinstance(comment.content_object, models.Answer): + delete_comment_view = 'delete_answer_comment' + elif isinstance(comment.content_object, models.Question): + delete_comment_view = 'delete_question_comment' + delete_url = reverse( + delete_comment_view, + kwargs = { + 'object_id': obj.id, + 'comment_id': comment.id + } + ) + except exceptions.PermissionDenied: + delete_url = '' + else: + delete_url = '' + + json_comments.append({'id' : comment.id, + 'object_id' : obj.id, + 'comment_age' : diff_date(comment.added_at), + 'text' : comment.html, + 'user_display_name' : comment.get_owner().username, + 'user_url' : comment.get_owner().get_profile_url(), + 'delete_url' : delete_url }) data = simplejson.dumps(json_comments) @@ -400,6 +406,12 @@ def __comments(request, obj):#non-view generic ajax handler to load comments to response = __generate_comments_json(obj, user) elif request.method == "POST": try: + if user.is_anonymous(): + msg = _('Sorry, you appear to be logged out and ' + \ + 'cannot post comments. Please ' + \ + '<a href="%(sign_in_url)s">sign in</a>.') % \ + {'sign_in_url': reverse('user_signin')} + raise exceptions.PermissionDenied(msg) user.post_comment( parent_post = obj, body_text = request.POST.get('comment') @@ -411,21 +423,49 @@ def __comments(request, obj):#non-view generic ajax handler to load comments to mimetype="application/json" ) return response + else: + raise Http404 + +def delete_comment( + request, + object_id='', + comment_id='', + commented_object_type=None): + """ajax handler to delete comment + """ -def delete_comment(request, object_id='', comment_id='', commented_object_type=None):#ajax handler to delete comment commented_object = None if commented_object_type == 'question': commented_object = models.Question elif commented_object_type == 'answer': commented_object = models.Answer - if request.is_ajax(): - comment = get_object_or_404(models.Comment, id=comment_id) - if auth.can_delete_comment(request.user, comment): + try: + if request.user.is_anonymous(): + msg = _('Sorry, you appear to be logged out and ' + \ + 'cannot delete comments. Please ' + \ + '<a href="%(sign_in_url)s">sign in</a>.') % \ + {'sign_in_url': reverse('user_signin')} + raise exceptions.PermissionDenied(msg) + if request.is_ajax(): + comment = get_object_or_404(models.Comment, id=comment_id) + + request.user.assert_can_delete_comment(comment) + obj = get_object_or_404(commented_object, id=object_id) + #todo: are the removed comments actually deleted? obj.comments.remove(comment) + #attn: recalc denormalized field obj.comment_count = obj.comment_count - 1 obj.save() - user = request.user - return __generate_comments_json(obj, user) - raise exceptions.PermissionDenied() + + return __generate_comments_json(obj, request.user) + + raise exceptions.PermissionDenied( + _('sorry, we seem to have some technical difficulties') + ) + except exceptions.PermissionDenied, e: + return HttpResponseForbidden( + str(e), + mimetype = 'application/json' + ) |