summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-11-24 01:32:54 -0500
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-11-24 01:32:54 -0500
commitf416fa800d156141cce7a5f0cdd8576b1a276ab5 (patch)
tree790cbf03e252bd46d61778cee0172549896986be
parent2f9594a522a551a35d12aa7697a0a7bf41237020 (diff)
downloadaskbot-f416fa800d156141cce7a5f0cdd8576b1a276ab5.tar.gz
askbot-f416fa800d156141cce7a5f0cdd8576b1a276ab5.tar.bz2
askbot-f416fa800d156141cce7a5f0cdd8576b1a276ab5.zip
comment editing works
-rw-r--r--askbot/exceptions.py9
-rw-r--r--askbot/models/__init__.py4
-rw-r--r--askbot/models/meta.py10
-rw-r--r--askbot/skins/default/media/js/i18n.js6
-rw-r--r--askbot/skins/default/media/js/post.js195
-rw-r--r--askbot/skins/default/media/js/utils.js15
-rwxr-xr-xaskbot/skins/default/media/style/style.css51
-rw-r--r--askbot/skins/default/templates/base.html1
-rw-r--r--askbot/skins/default/templates/macros.html3
-rw-r--r--askbot/skins/default/templates/question.html4
-rw-r--r--askbot/templatetags/extra_filters_jinja.py5
-rw-r--r--askbot/urls.py4
-rw-r--r--askbot/utils/decorators.py41
-rw-r--r--askbot/utils/http.py34
-rw-r--r--askbot/views/commands.py4
-rw-r--r--askbot/views/readers.py24
-rw-r--r--askbot/views/writers.py23
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">&nbsp;({{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