Browse Source

Asynchronus post voting + ajax helpers

Ralfp 12 years ago
parent
commit
41a42d2b8e

+ 11 - 0
misago/apps/errors.py

@@ -1,4 +1,6 @@
 from django.template import RequestContext
 from django.template import RequestContext
+from django.utils.translation import ugettext as _
+from misago.utils.views import json_response
 
 
 def error_not_implemented(request, *args, **kwargs):
 def error_not_implemented(request, *args, **kwargs):
     """Generic "NOT IMPLEMENTED!" Error"""
     """Generic "NOT IMPLEMENTED!" Error"""
@@ -6,6 +8,13 @@ def error_not_implemented(request, *args, **kwargs):
 
 
 
 
 def error_view(request, error, message):
 def error_view(request, error, message):
+    if request.is_ajax():
+        if not message:
+            if error == 404:
+                message = _("Requested page could not be loaded.")
+            if error == 403:
+                message = _("You don't have permission to see requested page.")
+        return json_response(request, status=error, message=message)
     response = request.theme.render_to_response(('error%s.html' % error),
     response = request.theme.render_to_response(('error%s.html' % error),
                                                 {
                                                 {
                                                  'message': message,
                                                  'message': message,
@@ -28,6 +37,8 @@ def error404(request, message=None):
 def error_banned(request, user=None, ban=None):
 def error_banned(request, user=None, ban=None):
     if not ban:
     if not ban:
         ban = request.ban
         ban = request.ban
+    if request.is_ajax():
+        return json_response(request, status=403, message=_("You are banned."))
     response = request.theme.render_to_response('error403_banned.html',
     response = request.theme.render_to_response('error403_banned.html',
                                                 {
                                                 {
                                                  'banned_user': user,
                                                  'banned_user': user,

+ 11 - 3
misago/apps/threadtype/jumps.py

@@ -9,6 +9,7 @@ from misago.messages import Message
 from misago.models import Forum, Thread, Post, Karma, WatchedThread
 from misago.models import Forum, Thread, Post, Karma, WatchedThread
 from misago.readstrackers import ThreadsTracker
 from misago.readstrackers import ThreadsTracker
 from misago.utils.pagination import make_pagination
 from misago.utils.pagination import make_pagination
+from misago.utils.views import json_response
 from misago.apps.threadtype.base import ViewBase
 from misago.apps.threadtype.base import ViewBase
 
 
 class JumpView(ViewBase):
 class JumpView(ViewBase):
@@ -40,9 +41,9 @@ class JumpView(ViewBase):
         except (Thread.DoesNotExist, Post.DoesNotExist):
         except (Thread.DoesNotExist, Post.DoesNotExist):
             return error404(self.request)
             return error404(self.request)
         except ACLError403 as e:
         except ACLError403 as e:
-            return error403(request, e.message)
+            return error403(request, e)
         except ACLError404 as e:
         except ACLError404 as e:
-            return error404(request, e.message)
+            return error404(request, e)
 
 
 
 
 class LastReplyBaseView(JumpView):
 class LastReplyBaseView(JumpView):
@@ -192,7 +193,6 @@ class UpvotePostBaseView(JumpView):
             vote.ip = request.session.get_ip(request)
             vote.ip = request.session.get_ip(request)
             vote.agent = request.META.get('HTTP_USER_AGENT')
             vote.agent = request.META.get('HTTP_USER_AGENT')
             self.make_vote(request, vote)
             self.make_vote(request, vote)
-            request.messages.set_flash(Message(_('Your vote has been saved.')), 'success', 'threads_%s' % self.post.pk)
             if vote.pk:
             if vote.pk:
                 vote.save(force_update=True)
                 vote.save(force_update=True)
             else:
             else:
@@ -219,6 +219,14 @@ class UpvotePostBaseView(JumpView):
             request.user.save(force_update=True)
             request.user.save(force_update=True)
             if self.post.user_id:
             if self.post.user_id:
                 self.post.user.save(force_update=True)
                 self.post.user.save(force_update=True)
+            if request.is_ajax():
+                return json_response(request, {
+                                               'score_total': self.post.upvotes - self.post.downvotes,
+                                               'score_upvotes': self.post.upvotes,
+                                               'score_downvotes': self.post.downvotes,
+                                               'user_vote': vote.score,
+                                              })
+            request.messages.set_flash(Message(_('Your vote has been saved.')), 'success', 'threads_%s' % self.post.pk)
             return self.redirect_to_post(self.post)
             return self.redirect_to_post(self.post)
         return view(self.request)
         return view(self.request)
     
     

+ 7 - 6
misago/utils/views.py

@@ -9,13 +9,14 @@ def redirect_message(request, message, type='info', owner=None):
     return redirect(reverse('index'))
     return redirect(reverse('index'))
 
 
 
 
+def json_response(request, json={}, status=200, message=None):
+    json.update({'code': status, 'message': unicode(message)})
+    response = json_dumps(json, sort_keys=True,  ensure_ascii=False)
+    return HttpResponse(response, content_type='application/json', status=status)
+
+
 def ajax_response(request, template=None, macro=None, vars={}, json={}, status=200, message=None):
 def ajax_response(request, template=None, macro=None, vars={}, json={}, status=200, message=None):
     html = ''
     html = ''
     if macro:
     if macro:
         html = request.theme.macro(template, macro, vars, context_instance=RequestContext(request));
         html = request.theme.macro(template, macro, vars, context_instance=RequestContext(request));
-    response = json_dumps(dict(json.items() + {
-                                       'code': status,
-                                       'message': message,
-                                       'html': html
-                                       }.items()), sort_keys=True,  ensure_ascii=False)
-    return HttpResponse(response, content_type='application/json', status=status)
+    return json_response(request, json.update({'html': html}), status, message)

+ 3 - 2
static/cranefly/css/cranefly.css

@@ -1099,8 +1099,9 @@ a.btn-link:hover,a.btn-link:active,a.btn-link:focus{opacity:0.9;filter:alpha(opa
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-rating span.post-like{color:#46a546;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-rating span.post-like{color:#46a546;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-rating span.post-hate{color:#cf402e;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-rating span.post-hate{color:#cf402e;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form{float:left;margin:0px;padding:0px;}.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link{float:right;margin:0px;margin-left:3.5px;opacity:1;filter:alpha(opacity=100);padding:0px;color:#999999;font-weight:normal;}.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link:active,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link:focus{text-decoration:underline;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form{float:left;margin:0px;padding:0px;}.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link{float:right;margin:0px;margin-left:3.5px;opacity:1;filter:alpha(opacity=100);padding:0px;color:#999999;font-weight:normal;}.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link:active,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link:focus{text-decoration:underline;}
-.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-like:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-like:active,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-like:focus{color:#46a546;}
-.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-hate:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-hate:active,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-hate:focus{color:#cf402e;}
+.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-like:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-like:active,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-like:focus,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-like:disabled{color:#46a546;}
+.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-hate:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-hate:active,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-hate:focus,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link.post-hate:disabled{color:#cf402e;}
+.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link:disabled:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link:disabled:active,.thread-body .post-wrapper .post-body .post-content .post-footer .post-rating form .btn-link:disabled:focus{text-decoration:none;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-actions{border-left:1px dotted #e7e7e7;float:right;padding:7px 14px;color:#999999;}.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions span,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions form{float:left;overflow:auto;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-actions{border-left:1px dotted #e7e7e7;float:right;padding:7px 14px;color:#999999;}.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions span,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions form{float:left;overflow:auto;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-actions form{margin:0px;padding:0px;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-actions form{margin:0px;padding:0px;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a{margin-left:14px;color:#999999;}.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a a:active{color:#333333;}
 .thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a{margin-left:14px;color:#999999;}.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a:hover,.thread-body .post-wrapper .post-body .post-content .post-footer .post-actions a a:active{color:#333333;}

+ 8 - 2
static/cranefly/css/cranefly/thread.less

@@ -233,16 +233,22 @@
                 }
                 }
 
 
                 &.post-like {
                 &.post-like {
-                  &:hover, &:active, &:focus {
+                  &:hover, &:active, &:focus, &:disabled {
                     color: @green;
                     color: @green;
                   }
                   }
                 }
                 }
 
 
                 &.post-hate {
                 &.post-hate {
-                  &:hover, &:active, &:focus {
+                  &:hover, &:active, &:focus, &:disabled {
                     color: @red;
                     color: @red;
                   }
                   }
                 }
                 }
+
+                &:disabled {
+                  &:hover, &:active, &:focus {
+                    text-decoration: none;
+                  }
+                }
               }
               }
             }
             }
           }
           }

+ 50 - 1
static/cranefly/js/cranefly.js

@@ -143,4 +143,53 @@ function link2player(link) {
 
 
 	// No link
 	// No link
 	return false;
 	return false;
-}
+}
+
+// Ajax errors handler
+$(document).ajaxError(function(event, jqXHR, settings) {
+	var responseJSON = jQuery.parseJSON(jqXHR.responseText);
+	if (responseJSON.message) {
+		alert(responseJSON.message);
+	}
+});
+
+// Ajax: Post votes
+$(function() {
+	$('.post-rating-actions').each(function() {
+		var action_parent = this;
+		var csrf_token = $(this).find('input[name="_csrf_token"]').val();
+		$(this).find('form').submit(function() {
+			var form = this;
+			$.post(this.action, {'_csrf_token': csrf_token}, "json").done(function(data, textStatus, jqXHR) {
+				// Reset stuff and set classess
+				$(action_parent).find('.post-score').removeClass('post-score-good post-score-bad');
+				if (data.score_total > 0) {
+					$(action_parent).find('.post-score-total').addClass('post-score-good');
+				} else if (data.score_total < 0) {
+					$(action_parent).find('.post-score-total').addClass('post-score-bad');
+				} 
+				if (data.score_upvotes > 0) {
+					$(action_parent).find('.post-score-upvotes').addClass('post-score-good');
+				}
+				if (data.score_downvotes > 0) {
+					$(action_parent).find('.post-score-downvotes').addClass('post-score-bad');
+				}
+
+				// Set votes
+				$(action_parent).find('.post-score-total').text(data.score_total);
+				$(action_parent).find('.post-score-upvotes').text(data.score_upvotes);
+				$(action_parent).find('.post-score-downvotes').text(data.score_downvotes);
+
+				// Disable and enable forms
+				if (data.user_vote == 1) {
+					$(action_parent).find('.form-upvote button').attr("disabled", "disabled");
+					$(action_parent).find('.form-downvote button').removeAttr("disabled");
+				} else {
+					$(action_parent).find('.form-upvote button').removeAttr("disabled");
+					$(action_parent).find('.form-downvote button').attr("disabled", "disabled");
+				}
+			}).error();
+			return false;
+		});
+	});
+});

+ 32 - 38
templates/cranefly/threads/thread.html

@@ -242,47 +242,41 @@
           </div>
           </div>
           <div class="post-footer">{% filter trim %}
           <div class="post-footer">{% filter trim %}
             {% if acl.threads.can_see_post_score(forum) %}
             {% if acl.threads.can_see_post_score(forum) %}
-            <div class="post-rating">
-              {% if acl.threads.can_see_post_score(forum) == 1 %}
-              <span class="post-score{% if (post.upvotes - post.downvotes) > 0 %} post-score-good{% elif (post.upvotes - post.downvotes) < 0 %} post-score-bad{% endif %}">{{ post.upvotes - post.downvotes }}</span>
-              {% elif acl.threads.can_see_post_score(forum) == 2%}
-              <span class="post-score{% if post.upvotes %} post-score-good{% endif %}">{{ post.upvotes }}</span>
-              {% endif %}
-              {% if user.is_authenticated() and user.pk != post.user_id and acl.threads.can_upvote_posts(forum) %}
-              {% if post.karma_vote and post.karma_vote.score > 0 %}
-              <span class="post-like">{% trans %}Like{% endtrans %}</span>
-              {% else %}
-              <form action="{% url 'post_upvote' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline" method="post">
-                <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
-                <button type="submit" class="btn btn-link post-like">{% trans %}Like{% endtrans %}</button>
-              </form>
-              {% endif %}
-              {% else %}
-              <span class="post-{% if post.upvotes %}like{% else %}neutral{% endif %}">{% trans %}Likes{% endtrans %}</span>
-              {% endif %}
-            {% if acl.threads.can_see_post_score(forum) == 2 %}
-            </div>
-            <div class="post-rating">
-              <span class="post-score{% if post.downvotes %} post-score-bad{% endif %}">{{ post.downvotes }}</span>
-            {% endif %}
-              {% if user.is_authenticated() and user.pk != post.user_id and acl.threads.can_downvote_posts(forum) %}
-              {% if post.karma_vote and post.karma_vote.score < 0 %}
-              <span class="post-hate">{% trans %}Hate{% endtrans %}</span>
-              {% else %}
-              <form action="{% url 'post_downvote' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline" method="post">
-                <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
-                <button type="submit" class="btn btn-link post-hate">{% trans %}Hate{% endtrans %}</button>
-              </form>
+            <div{% if user.is_authenticated() and user.pk != post.user_id %} class="post-rating-actions"{% endif %}>
+              <div class="post-rating">
+                {% if acl.threads.can_see_post_score(forum) == 1 %}
+                <span class="post-score post-score-total{% if (post.upvotes - post.downvotes) > 0 %} post-score-good{% elif (post.upvotes - post.downvotes) < 0 %} post-score-bad{% endif %}">{{ post.upvotes - post.downvotes }}</span>
+                {% elif acl.threads.can_see_post_score(forum) == 2%}
+                <span class="post-score post-score-upvotes{% if post.upvotes %} post-score-good{% endif %}">{{ post.upvotes }}</span>
+                {% endif %}
+                {% if user.is_authenticated() and user.pk != post.user_id and acl.threads.can_upvote_posts(forum) %}
+                <form action="{% url 'post_upvote' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline form-upvote" method="post">
+                  <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+                  <button type="submit" class="btn btn-link post-like"{% if post.karma_vote and post.karma_vote.score > 0 %} disabled="disabled"{% endif %}>{% trans %}Like{% endtrans %}</button>
+                </form>
+                {% else %}
+                <span class="post-{% if post.upvotes %}like{% else %}neutral{% endif %}">{% trans %}Likes{% endtrans %}</span>
+                {% endif %}
+              {% if acl.threads.can_see_post_score(forum) == 2 %}
+              </div>
+              <div class="post-rating">
+                <span class="post-score post-score-downvotes{% if post.downvotes %} post-score-bad{% endif %}">{{ post.downvotes }}</span>
               {% endif %}
               {% endif %}
-              {% elif acl.threads.can_see_post_score(forum) == 2 %}
-              <span class="post-{% if post.downvotes %}hate{% else %}neutral{% endif %}">{% trans %}Hates{% endtrans %}</span>
+                {% if user.is_authenticated() and user.pk != post.user_id and acl.threads.can_downvote_posts(forum) %}
+                <form action="{% url 'post_downvote' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline form-downvote" method="post">
+                  <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+                  <button type="submit" class="btn btn-link post-hate"{% if post.karma_vote and post.karma_vote.score < 0 %} disabled="disabled"{% endif %}>{% trans %}Hate{% endtrans %}</button>
+                </form>
+                {% elif acl.threads.can_see_post_score(forum) == 2 %}
+                <span class="post-{% if post.downvotes %}hate{% else %}neutral{% endif %}">{% trans %}Hates{% endtrans %}</span>
+                {% endif %}
+              </div>
+              {% if acl.threads.can_see_post_votes(forum) %}
+              <div class="post-rating">
+                <a href="{% url 'post_votes' thread=thread.pk, slug=thread.slug, post=post.pk %}">{% trans %}Show Votes{% endtrans %}</a>
+              </div>
               {% endif %}
               {% endif %}
             </div>
             </div>
-            {% if acl.threads.can_see_post_votes(forum) %}
-            <div class="post-rating">
-              <a href="{% url 'post_votes' thread=thread.pk, slug=thread.slug, post=post.pk %}">{% trans %}Show Votes{% endtrans %}</a>
-            </div>
-            {% endif %}
             {% endif %}
             {% endif %}
 
 
             {% if user.is_authenticated() %}
             {% if user.is_authenticated() %}