diff options
-rw-r--r-- | askbot/exceptions.py | 9 | ||||
-rw-r--r-- | askbot/models/__init__.py | 4 | ||||
-rw-r--r-- | askbot/models/meta.py | 10 | ||||
-rw-r--r-- | askbot/skins/default/media/js/i18n.js | 6 | ||||
-rw-r--r-- | askbot/skins/default/media/js/post.js | 195 | ||||
-rw-r--r-- | askbot/skins/default/media/js/utils.js | 15 | ||||
-rwxr-xr-x | askbot/skins/default/media/style/style.css | 51 | ||||
-rw-r--r-- | askbot/skins/default/templates/base.html | 1 | ||||
-rw-r--r-- | askbot/skins/default/templates/macros.html | 3 | ||||
-rw-r--r-- | askbot/skins/default/templates/question.html | 4 | ||||
-rw-r--r-- | askbot/templatetags/extra_filters_jinja.py | 5 | ||||
-rw-r--r-- | askbot/urls.py | 4 | ||||
-rw-r--r-- | askbot/utils/decorators.py | 41 | ||||
-rw-r--r-- | askbot/utils/http.py | 34 | ||||
-rw-r--r-- | askbot/views/commands.py | 4 | ||||
-rw-r--r-- | askbot/views/readers.py | 24 | ||||
-rw-r--r-- | askbot/views/writers.py | 23 |
17 files changed, 274 insertions, 159 deletions
diff --git a/askbot/exceptions.py b/askbot/exceptions.py index eb4541bd..ed60cc8a 100644 --- a/askbot/exceptions.py +++ b/askbot/exceptions.py @@ -1,4 +1,13 @@ from django.core import exceptions +from django.utils.translation import ugettext as _ + +class LoginRequired(exceptions.PermissionDenied): + """raised when an operation required a logged + in user""" + def __init__(self, msg = None): + if msg is None: + msg = _('Sorry, but anonymous visitors cannot access this function') + super(LoginRequired, self).__init__(msg) class InsufficientReputation(exceptions.PermissionDenied): """exception class to indicate that permission diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 4eedaf0d..bf8c4d36 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -290,8 +290,10 @@ def user_assert_can_edit_comment(self, comment = None): if comment.user == self: now = datetime.datetime.now() if now - comment.added_at > datetime.timedelta(0, 600): + if comment.is_last(): + return error_message = _( - 'Sorry, but comments can be edited only within 10 minutes from posting' + 'Sorry, comments (except the last one) are editable only within 10 minutes from posting' ) raise django_exceptions.PermissionDenied(error_message) return diff --git a/askbot/models/meta.py b/askbot/models/meta.py index 88b41c9e..729ea42c 100644 --- a/askbot/models/meta.py +++ b/askbot/models/meta.py @@ -246,5 +246,15 @@ class Comment(base.MetaContent, base.UserContent): def get_latest_revision_number(self): return 1 + def is_last(self): + """True if there are no newer comments on + the related parent object + """ + return Comment.objects.filter( + added_at__gt = self.added_at, + object_id = self.object_id, + content_type = self.content_type + ).count() == 0 + def __unicode__(self): return self.comment diff --git a/askbot/skins/default/media/js/i18n.js b/askbot/skins/default/media/js/i18n.js index d326bdd7..c3b645f2 100644 --- a/askbot/skins/default/media/js/i18n.js +++ b/askbot/skins/default/media/js/i18n.js @@ -127,7 +127,7 @@ var i18nEn = { 'post recovered':'your post is now restored!', 'post deleted':'your post has been deleted', 'confirm delete comment':'do you really want to delete this comment?', - 'can write':'have ', + 'can write':' ', 'tablimits info':'up to 5 tags, no more than 20 characters each', 'content minchars': 'please enter more than {0} characters', 'title minchars':"please enter at least {0} characters", @@ -135,7 +135,9 @@ var i18nEn = { 'cannot flag message as offensive twice':'cannot flag message as offensive twice ', 'edit':'edit', 'click to edit this comment':'click to edit this comment', - 'confirm abandon comment':'Are you sure you do not want to post this comment?' + 'confirm abandon comment':'Are you sure you do not want to post this comment?', + 'save comment': 'save comment', + 'enter more characters': 'please enter at least {0} more characters' }; var i18nFi = { diff --git a/askbot/skins/default/media/js/post.js b/askbot/skins/default/media/js/post.js index ddae6d48..3cb031c1 100644 --- a/askbot/skins/default/media/js/post.js +++ b/askbot/skins/default/media/js/post.js @@ -842,26 +842,39 @@ WrappedElement.prototype.dispose = function(){ this._element.remove(); }; -var EditLink = function(edit_handler){ - WrappedElement.call(this) - this._edit_handler = edit_handler; +var SimpleControl = function(){ + WrappedElement.call(this); + this._handler = null; +}; +inherits(SimpleControl, WrappedElement); + +SimpleControl.prototype.setHandler = function(handler){ + this._handler = handler; +}; + +var EditLink = function(){ + SimpleControl.call(this) }; -inherits(EditLink, WrappedElement); +inherits(EditLink, SimpleControl); EditLink.prototype.createDom = function(){ - this._element = $('<a></a>'); + var element = $('<a></a>'); + element.addClass('edit'); + this.decorate(element); +}; + +EditLink.prototype.decorate = function(element){ + this._element = element; this._element.attr('title', $.i18n._('click to edit this comment')); - this._element.css('display', 'none'); this._element.html($.i18n._('edit')); - setupButtonEventHandlers(this._element, this._edit_handler); + setupButtonEventHandlers(this._element, this._handler); }; -var DeleteIcon = function(title, delete_handler){ - WrappedElement.call(this); +var DeleteIcon = function(title){ + SimpleControl.call(this); this._title = title; - this._delete_handler = delete_handler; }; -inherits(DeleteIcon, WrappedElement); +inherits(DeleteIcon, SimpleControl); DeleteIcon.prototype.decorate = function(element){ this._element = element; @@ -870,7 +883,7 @@ DeleteIcon.prototype.decorate = function(element){ this._element.attr('class', 'delete-icon'); this._element.attr('src', img); this._element.attr('title', this._title); - setupButtonEventHandlers(this._element, this._delete_handler); + setupButtonEventHandlers(this._element, this._handler); this._element.mouseover(function(e){ $(this).attr('src', imgHover); }); @@ -909,12 +922,18 @@ EditCommentForm.prototype.attachTo = function(comment, mode){ comment.getElement().after(this.getElement()); comment.getElement().hide(); this._comment_widget.hideButton(); + if (this._type == 'add'){ + this._submit_btn.html($.i18n._('add comment')); + } + else { + this._submit_btn.html($.i18n._('save comment')); + } this.getElement().show(); this.focus(); putCursorAtEnd(this._textarea); }; -EditCommentForm.prototype.makeCounterUpdater = function(){ +EditCommentForm.prototype.getCounterUpdater = function(){ //returns event handler var counter = this._text_counter; var handler = function(){ @@ -928,10 +947,21 @@ EditCommentForm.prototype.makeCounterUpdater = function(){ if (length2 < 0){ length2 = Math.round(0.9*maxCommentLength); } - var color = length > length2 ? "#f00" : length > length1 ? "#f60" : "#999"; - counter.html($.i18n._('can write') + ' ' + + + var color = 'maroon'; + if (length === 0){ + var feedback = $.i18n._('title minchars').replace('{0}', 10); + } + else if (length < 10){ + var feedback = $.i18n._('enter more characters').replace('{0}', 10 - length); + } + else { + color = length > length2 ? "#f00" : length > length1 ? "#f60" : "#999"; + var feedback = $.i18n._('can write') + ' ' + (maxCommentLength - length) + ' ' + - $.i18n._('characters')).css("color", color); + $.i18n._('characters'); + } + counter.html(feedback).css('color', color); }; return handler; }; @@ -949,17 +979,15 @@ EditCommentForm.prototype.canCancel = function(){ } this.focus(); return false; -} +}; -EditCommentForm.prototype.makeEscapeHandler = function(){ +EditCommentForm.prototype.getCancelHandler = function(){ var form = this; - return function(e){ - if ((e.which && e.which == 27) || (e.keyCode && e.keyCode == 27)){ - if (form.canCancel()){ - form.detach(); - } - return false; - } + return function(){ + if (form.canCancel()){ + form.detach(); + } + return false; }; }; @@ -989,18 +1017,20 @@ EditCommentForm.prototype.createDom = function(){ this._element.append(div); div.append(this._textarea); - this._submit_btn = $('<input type="submit" />'); + this._text_counter = $('<span></span>').attr('class', 'counter'); + div.append(this._text_counter); + this._submit_btn = $('<button class="submit small"></button>'); div.append(this._submit_btn); - this._cancel_btn = $('<button></button>'); + this._cancel_btn = $('<button class="submit small"></button>'); this._cancel_btn.html($.i18n._('cancel')); div.append(this._cancel_btn); - div.append('<br />'); - this._text_counter = $('<span></span>').attr('class', 'text-counter'); - div.append(this._text_counter); - div.append('<span class="form-error"></span>'); - var update_counter = this.makeCounterUpdater(); - var escape_handler = this.makeEscapeHandler(); + setupButtonEventHandlers(this._submit_btn, this.getSaveHandler()); + setupButtonEventHandlers(this._cancel_btn, this.getCancelHandler()); + + var update_counter = this.getCounterUpdater(); + var escape_handler = makeKeyHandler(27, this.getCancelHandler()); + var save_handler = makeKeyHandler(13, this.getSaveHandler()); this._textarea.attr('name', 'comment') .attr('cols', 60) .attr('rows', 5) @@ -1008,15 +1038,9 @@ EditCommentForm.prototype.createDom = function(){ .blur(update_counter) .focus(update_counter) .keyup(update_counter) - .keyup(escape_handler); + .keyup(escape_handler) + .keydown(save_handler); this._textarea.val(this._text); - - setupFormValidation( - this._element, - { comment: { required: true, minlength: 10} }, - '', - this.getSaveHandler() - ); }; EditCommentForm.prototype.enableButtons = function(){ @@ -1037,24 +1061,32 @@ EditCommentForm.prototype.reset = function(){ }; EditCommentForm.prototype.confirmAbandon = function(){ - this.focus(); + this.focus(true); this._textarea.addClass('highlight'); var answer = confirm($.i18n._('confirm abandon comment')); this._textarea.removeClass('highlight'); return answer; }; -EditCommentForm.prototype.focus = function(){ +EditCommentForm.prototype.focus = function(hard){ this._textarea.focus(); - window.location.hash = this._id; + if (hard === true){ + $(this._textarea).scrollTop(); + } }; EditCommentForm.prototype.getSaveHandler = function(){ var me = this; return function(){ + var text = me._textarea.val(); + if (text.length <= 10){ + me.focus(); + return; + } + var post_data = { - comment: me._textarea.val(), + comment: text }; if (me._type == 'edit'){ @@ -1095,11 +1127,6 @@ EditCommentForm.prototype.getSaveHandler = function(){ }; }; -EditCommentForm.prototype.editComment = function(comment){ - this.detach(); - this.attachTo(comment, 'edit'); -}; - //a single instance to reuse var editCommentForm = new EditCommentForm(); @@ -1112,10 +1139,14 @@ var Comment = function(widget, data){ this._delete_prompt = $.i18n._('delete this comment'); if (data && data['is_deletable']){ this._deletable = data['is_deletable']; - this._editable = data['is_deletable']; } else { this._deletable = false; + } + if (data && data['is_editable']){ + this._editable = data['is_deletable']; + } + else { this._editable = false; } }; @@ -1129,16 +1160,18 @@ Comment.prototype.decorate = function(element){ var delete_img = this._element.find('img.delete-icon'); if (delete_img.length > 0){ this._deletable = true; + this._delete_icon = new DeleteIcon(this.deletePrompt); + this._delete_icon.setHandler(this.getDeleteHandler()); + this._delete_icon.decorate(delete_img); + } + var edit_link = this._element.find('a.edit'); + if (edit_link.length > 0){ this._editable = true; - - var del = new DeleteIcon(this.deletePrompt, this.getDeleteHandler()); - del.decorate(delete_img); - - var edit_link = new EditLink(this.getEditHandler()); - this._element.append(edit_link.getElement()); - this._element.mouseover(function(){edit_link.getElement().show()}); - this._element.mouseout(function(){edit_link.getElement().hide()}); + this._edit_link = new EditLink(); + this._edit_link.setHandler(this.getEditHandler()); + this._edit_link.decorate(edit_link); } + this._blank = false; }; @@ -1191,16 +1224,16 @@ Comment.prototype.setContent = function(data){ this._element.append(this._comment_age); this._element.append(')'); - if (this._deletable){ - this._delete_icon = new DeleteIcon(this._delete_prompt, this.getDeleteHandler); - this._element.append(this._delete_icon.getElement()); - } if (this._editable){ - this._edit_link = new EditLink(this.getEditHandler()); + this._edit_link = new EditLink(); + this._edit_link.setHandler(this.getEditHandler()) this._element.append(this._edit_link.getElement()); - var edit_link = this._edit_link; - this._element.mouseover(function(){edit_link.getElement().show()}); - this._element.mouseout(function(){edit_link.getElement().hide()}); + } + + if (this._deletable){ + this._delete_icon = new DeleteIcon(this._delete_prompt); + this._delete_icon.setHandler(this.getDeleteHandler()); + this._element.append(this._delete_icon.getElement()); } this._blank = false; }; @@ -1237,12 +1270,15 @@ Comment.prototype.loadText = function(on_load_handler){ var me = this; $.ajax({ type: "GET", - url: askbot['urls']['getCommentText'], + url: askbot['urls']['getComment'], data: {id: this._data['id']}, success: function(json){ me._data['text'] = json['text']; on_load_handler() - } + }, + error: function(xhr, textStatus, exception) { + showMessage(me.getElement(), xhr.responseText, 'after'); + }, }); }; @@ -1259,13 +1295,14 @@ Comment.prototype.getEditHandler = function(){ var comment = this; return function(){ if (editCommentForm.canCancel()){ + editCommentForm.detach(); if (comment.hasText()){ - editCommentForm.editComment(comment); + editCommentForm.attachTo(comment, 'edit'); } else { comment.loadText( function(){ - editCommentForm.editComment(comment); + editCommentForm.attachTo(comment, 'edit'); } ); } @@ -1275,9 +1312,10 @@ Comment.prototype.getEditHandler = function(){ Comment.prototype.getDeleteHandler = function(){ var comment = this; + var del_icon = this._delete_icon; return function(){ if (confirm($.i18n._('confirm delete comment'))){ - $(this).hide(); + comment.getElement().hide(); $.ajax({ type: 'POST', url: askbot['urls']['deleteComment'], @@ -1286,8 +1324,8 @@ Comment.prototype.getDeleteHandler = function(){ comment.dispose(); }, error: function(xhr, textStatus, exception) { - $(this).show(); - showMessage(me._delete_icon, xhr.responseText); + comment.getElement().show() + showMessage(del_icon.getElement(), xhr.responseText); }, dataType: "json" }); @@ -1353,6 +1391,9 @@ PostCommentsWidget.prototype.hideButton = function(){ }; PostCommentsWidget.prototype.showButton = function(){ + if (this._is_truncated === false){ + this._activate_button.html(askbot['messages']['addComment']); + } this._activate_button.show(); } @@ -1403,11 +1444,15 @@ PostCommentsWidget.prototype.getDenyHandler = function(){ PostCommentsWidget.prototype.reloadAllComments = function(callback){ var post_data = {post_id: this._post_id, post_type: this._post_type}; + var me = this; $.ajax({ type: "GET", url: askbot['urls']['postComments'], data: post_data, - success: callback, + success: function(json){ + callback(json); + me._is_truncated = false; + }, dataType: "json" }); }; diff --git a/askbot/skins/default/media/js/utils.js b/askbot/skins/default/media/js/utils.js index 08d6717d..a4dd1aa6 100644 --- a/askbot/skins/default/media/js/utils.js +++ b/askbot/skins/default/media/js/utils.js @@ -27,16 +27,23 @@ var showMessage = function(element, msg, where) { div.fadeIn("fast"); }; -var setupButtonEventHandlers = function(button, callback){ - button.keydown(function(e){ - if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)){ + +var makeKeyHandler = function(key, callback){ + return function(e){ + if ((e.which && e.which == key) || (e.keyCode && e.keyCode == key)){ callback(); return false; } - }); + }; +}; + + +var setupButtonEventHandlers = function(button, callback){ + button.keydown(makeKeyHandler(13, callback)); button.click(callback); }; + var putCursorAtEnd = function(element){ var el = element.get()[0]; if (el.setSelectionRange){ diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css index 3a7dc684..1b3cb9e4 100755 --- a/askbot/skins/default/media/style/style.css +++ b/askbot/skins/default/media/style/style.css @@ -1304,6 +1304,12 @@ a:hover.medal { font-size: 90%; } +.comments { + font-size: 11px; + line-height: 15px; + clear: both; +} + .comments div.controls { clear: both; background: url(../images/gray-up-arrow-h18px.png) no-repeat; @@ -1313,8 +1319,13 @@ a:hover.medal { } .comments textarea { - height: 6em; - margin-bottom: 4px; + height: 48px; + width: 664px; + margin: 0 0 4px 0; + font-family: sans-serif; + font-size: 11px; + line-height: 15px; + padding: 2px 0 0 2px; } .comments input { @@ -1324,6 +1335,12 @@ a:hover.medal { width: 100px; } +.comments .counter { + display: inline-block; + width: 245px; + vertical-align: top; +} + .comments .controls a { color: #888888; padding: 0 3px 2px; @@ -1337,7 +1354,6 @@ a:hover.medal { span.text-counter { margin-right: 20px; - font-size: 11px; } span.form-error { @@ -2176,7 +2192,7 @@ div.started .reputation-score { a.comment { background: #EEE; color: #993300; - padding: 4px; + padding: 5px; } a.permLink { @@ -2257,6 +2273,17 @@ p.space-above { text-decoration: underline; } +.submit.small { + height:20px; + font-weight:normal; + font-size:12px; + padding:0 5px; +} +.submit.small:hover { + text-decoration:none; +} + + .ask-body { padding-right: 10px; } @@ -2399,14 +2426,14 @@ p.space-above { .comment { border-top: 1px dotted #ccccce; margin: 0; - font-size: 11px; - color: #444444; - padding: 5px 0 5px 0; + color: #444; + padding: 2px 3px 5px 3px; } .delete-icon { vertical-align: middle; - padding-left: 3px; + padding-left: 1px; + margin-bottom:3px; } /* these need to go */ @@ -2565,9 +2592,6 @@ ul.form-horizontal-rows li input { margin: 4px 8px 0 0; } -.comments { - clear: both; -} .post-update-info p.tip { color: #444; @@ -2773,6 +2797,11 @@ img.gravatar { } /* Pretty printing styles. Used with prettify.js. */ +a.edit { + padding-left:3px; + color: #145bff; +} + .str { color: #080; } .kwd { color: #008; } .com { color: #800; } diff --git a/askbot/skins/default/templates/base.html b/askbot/skins/default/templates/base.html index c8d7ab76..1bf3e572 100644 --- a/askbot/skins/default/templates/base.html +++ b/askbot/skins/default/templates/base.html @@ -25,6 +25,7 @@ askbot['data'] = {}; askbot['urls'] = {}; askbot['settings'] = {}; + askbot['messages'] = {}; </script> {% block forejs %} {% endblock %} diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html index d7d05358..0db6eb53 100644 --- a/askbot/skins/default/templates/macros.html +++ b/askbot/skins/default/templates/macros.html @@ -268,6 +268,9 @@ poor design of the data or methods on data objects #} href="{{comment.user.get_profile_url()}}" >{{comment.user.username}}</a> <span class="age"> ({{comment.added_at|diff_date}})</span> + {% if user|can_edit_comment(comment) %} + <a class="edit">{% trans %}edit{% endtrans %}</a> + {% endif %} {% if user|can_delete_comment(comment) %} <img class="delete-icon" src="{{"/images/close-small.png"|media}}" diff --git a/askbot/skins/default/templates/question.html b/askbot/skins/default/templates/question.html index 22c392d5..b944b48f 100644 --- a/askbot/skins/default/templates/question.html +++ b/askbot/skins/default/templates/question.html @@ -415,7 +415,9 @@ var maxCommentLength = {{settings.MAX_COMMENT_LENGTH}}; askbot['urls']['postComments'] = '{% url post_comments %}'; askbot['urls']['editComment'] = '{% url edit_comment %}'; - askbot['urls']['getCommentText'] = '{% url get_comment_text %}'; + askbot['urls']['deleteComment'] = '{% url delete_comment %}'; + askbot['urls']['getComment'] = '{% url get_comment %}'; + askbot['messages']['addComment'] = '{% trans %}add comment{% endtrans %}'; </script> <script type='text/javascript' src='{{"/js/wmd/showdown.js"|media}}'></script> <script type='text/javascript' src='{{"/js/wmd/wmd.js"|media}}'></script> diff --git a/askbot/templatetags/extra_filters_jinja.py b/askbot/templatetags/extra_filters_jinja.py index 70fd6650..71965517 100644 --- a/askbot/templatetags/extra_filters_jinja.py +++ b/askbot/templatetags/extra_filters_jinja.py @@ -136,6 +136,11 @@ can_post_comment = make_template_filter_from_permission_assertion( filter_name = 'can_post_comment' ) +can_edit_comment = make_template_filter_from_permission_assertion( + assertion_name = 'assert_can_edit_comment', + filter_name = 'can_edit_comment' + ) + can_close_question = make_template_filter_from_permission_assertion( assertion_name = 'assert_can_close_question', filter_name = 'can_close_question' diff --git a/askbot/urls.py b/askbot/urls.py index 21956eaf..27c768b2 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -123,8 +123,8 @@ urlpatterns = patterns('', ), url( r'^comment/get_text/$', - app.readers.get_comment_text, - name='get_comment_text' + app.readers.get_comment, + name='get_comment' ), #place general question item in the end of other operations url( diff --git a/askbot/utils/decorators.py b/askbot/utils/decorators.py index 873bc5ae..8a8d2fcf 100644 --- a/askbot/utils/decorators.py +++ b/askbot/utils/decorators.py @@ -3,6 +3,7 @@ import time import os import datetime import functools +import logging from django.conf import settings from django.http import HttpResponse, HttpResponseForbidden, Http404 from django.utils import simplejson @@ -35,7 +36,40 @@ def ajax_login_required(view_func): return wrap -def ajax_method(view_func): +def anonymous_forbidden(view_func): + @functools.wraps(view_func) + def wrapper(request, *args, **kwargs): + if request.user.is_anonymous(): + raise askbot_exceptions.LoginRequired() + return view_func(request, *args, **kwargs) + return wrapper + + +def get_only(view_func): + @functools.wraps(view_func) + def wrapper(request, *args, **kwargs): + if request.method != 'GET': + raise django_exceptions.PermissionDenied( + 'request method %s is not supported for this function' % \ + request.method + ) + return view_func(request, *args, **kwargs) + return wrapper + + +def post_only(view_func): + @functools.wraps(view_func) + def wrapper(request, *args, **kwargs): + if request.method != 'POST': + raise django_exceptions.PermissionDenied( + 'request method %s is not supported for this function' % \ + request.method + ) + return view_func(request, *args, **kwargs) + return wrapper + + +def ajax_only(view_func): @functools.wraps(view_func) def wrapper(request,*args,**kwargs): if not request.is_ajax(): @@ -46,7 +80,8 @@ def ajax_method(view_func): message = unicode(e) if message == '': message = _('Oops, apologies - there was some error') - return HttpResponse(message, status = 404) + logging.debug(message) + return HttpResponse(message, status = 404, mimetype='application/json') if isinstance(data, HttpResponse):#is this used? data.mimetype = 'application/json' @@ -56,12 +91,12 @@ def ajax_method(view_func): return HttpResponse(json,mimetype='application/json') return wrapper + try: PROFILE_LOG_BASE = settings.PROFILE_LOG_BASE except: PROFILE_LOG_BASE = "/tmp" - def profile(log_file): """Profile some callable. diff --git a/askbot/utils/http.py b/askbot/utils/http.py deleted file mode 100644 index a3aea72d..00000000 --- a/askbot/utils/http.py +++ /dev/null @@ -1,34 +0,0 @@ -"""http utils similar to django's but with more -specific properties -""" -import logging -from django.http import HttpResponse -from django.utils import simplejson - -class JsonResponse(HttpResponse): - """response class that receives a dictionary - and returns it serialized with simplejson - and response type application/json - """ - def __init__(self, *args, **kwargs): - mimetype = kwargs.pop('mimetype', None) - if mimetype: - raise KeyError('JsonResponse does not accept mimetype variable') - kwargs['mimetype'] = 'application/json' - string_data = simplejson.dumps(kwargs.pop('data', '')) - super(JsonResponse, self).__init__(string_data, *args, **kwargs) - -class JsonLoggingErrorResponse(JsonResponse): - """like json response, only with empty content - and status=500, plus logs an error message - """ - def __init__(self, error, *args, **kwargs): - status = kwargs.pop('status', None) - if status: - raise KeyError('JsonLoggingErrorResponse does not accept status') - log_level = kwargs.pop('log_level', 'debug') - assert(log_level in ('debug', 'critical')) - log = getattr(logging, log_level) - log('ajax error ' + unicode(error)) - kwargs['status'] = 500 - super(JsonLoggingErrorResponse, self).__init__(*args, **kwargs) diff --git a/askbot/views/commands.py b/askbot/views/commands.py index 74f71fd6..302f0200 100644 --- a/askbot/views/commands.py +++ b/askbot/views/commands.py @@ -21,7 +21,7 @@ from askbot.forms import CloseForm from askbot import auth from django.core.urlresolvers import reverse from django.contrib.auth.decorators import login_required -from askbot.utils.decorators import ajax_method, ajax_login_required +from askbot.utils.decorators import ajax_only, ajax_login_required from askbot.templatetags import extra_filters as template_filters from askbot.skins.loaders import ENV from askbot import const @@ -363,7 +363,7 @@ def ajax_toggle_ignored_questions(request):#ajax tagging and tag-filtering syste request.user.hide_ignored_questions = new_hide_setting request.user.save() -@ajax_method +@ajax_only def ajax_command(request): """view processing ajax commands - note "vote" and view others do it too """ diff --git a/askbot/views/readers.py b/askbot/views/readers.py index 7e6b1691..ec0b4f20 100644 --- a/askbot/views/readers.py +++ b/askbot/views/readers.py @@ -36,8 +36,7 @@ from askbot import auth from askbot.utils import markup from askbot.utils.forms import get_next_url from askbot.utils.functions import not_a_robot_request -from askbot.utils.decorators import profile -from askbot.utils.http import JsonResponse, JsonLoggingErrorResponse +from askbot.utils.decorators import anonymous_forbidden, ajax_only, get_only from askbot.search.state_manager import SearchState from askbot.templatetags import extra_tags from askbot.templatetags import extra_filters @@ -557,20 +556,15 @@ def revisions(request, id, object_name=None): template = ENV.get_template('revisions.html') return HttpResponse(template.render(context)) -def get_comment_text(request): +@ajax_only +@anonymous_forbidden +@get_only +def get_comment(request): """returns text of a comment by id via ajax response requires request method get and request must be ajax """ - if request.is_ajax(): - if request.method == "GET": - try: - id = int(request.GET['id']) - comment = Comment.objects.get(id = id) - return JsonResponse(data={'text': comment.comment}) - except Exception, e: - return JsonLoggingErrorResponse(e) - else: - return JsonResponse(status=400) - else: - raise Http404() + id = int(request.GET['id']) + comment = Comment.objects.get(id = id) + request.user.assert_can_edit_comment(comment) + return {'text': comment.comment} diff --git a/askbot/views/writers.py b/askbot/views/writers.py index c6b35061..e431d5fd 100644 --- a/askbot/views/writers.py +++ b/askbot/views/writers.py @@ -26,8 +26,9 @@ from askbot.views.readers import _get_tags_cache_json from askbot import forms from askbot import models from askbot.skins.loaders import ENV -from askbot.utils.decorators import ajax_method +from askbot.utils.decorators import ajax_only from askbot.templatetags.extra_tags import diff_date +from askbot.templatetags import extra_filters_jinja as template_filters # used in index page INDEX_PAGE_SIZE = 20 @@ -408,8 +409,11 @@ def __generate_comments_json(obj, user):#non-view generates json data for the po is_deletable = True except exceptions.PermissionDenied: is_deletable = False + is_editable = template_filters.can_edit_comment(comment.user, comment) else: is_deletable = False + is_editable = False + comment_owner = comment.get_owner() json_comments.append({'id' : comment.id, @@ -420,6 +424,7 @@ def __generate_comments_json(obj, user):#non-view generates json data for the po 'user_url': comment_owner.get_profile_url(), 'user_id': comment_owner.id, 'is_deletable': is_deletable, + 'is_editable': is_editable, }) data = simplejson.dumps(json_comments) @@ -463,7 +468,7 @@ def post_comments(request):#non-view generic ajax handler to load comments to an else: raise Http404 -@ajax_method +@ajax_only def edit_comment(request): if request.user.is_authenticated(): comment_id = int(request.POST['comment_id']) @@ -473,11 +478,9 @@ def edit_comment(request): comment = comment, body_text = request.POST['comment'] ) - try: - request.user.assert_can_delete_comment(comment) - is_deletable = True - except exceptions.PermissionDenied: - is_deletable = False + + is_deletable = template_filters.can_delete_comment(comment.user, comment) + is_editable = template_filters.can_edit_comment(comment.user, comment) return {'id' : comment.id, 'object_id': comment.content_object.id, @@ -487,13 +490,14 @@ def edit_comment(request): 'user_url': comment.user.get_profile_url(), 'user_id': comment.user.id, 'is_deletable': is_deletable, + 'is_editable': is_editable, } else: raise exceptions.PermissionDenied( _('Sorry, anonymous users cannot edit comments') ) -def delete_comment(request, object_id='', comment_id=''): +def delete_comment(request): """ajax handler to delete comment """ try: @@ -504,8 +508,9 @@ def delete_comment(request, object_id='', comment_id=''): {'sign_in_url': reverse('user_signin')} raise exceptions.PermissionDenied(msg) if request.is_ajax(): - comment = get_object_or_404(models.Comment, id=comment_id) + comment_id = request.POST['comment_id'] + comment = get_object_or_404(models.Comment, id=comment_id) request.user.assert_can_delete_comment(comment) obj = comment.content_object |