summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--hyperkitty/lib/__init__.py16
-rw-r--r--hyperkitty/lib/voting.py54
-rw-r--r--hyperkitty/static/js/hyperkitty.js14
-rw-r--r--hyperkitty/templates/messages/like_form.html18
-rw-r--r--hyperkitty/tests/test_views.py98
-rw-r--r--hyperkitty/views/list.py8
-rw-r--r--hyperkitty/views/message.py65
-rw-r--r--hyperkitty/views/thread.py17
8 files changed, 208 insertions, 82 deletions
diff --git a/hyperkitty/lib/__init__.py b/hyperkitty/lib/__init__.py
index b3fa6c4..895e64e 100644
--- a/hyperkitty/lib/__init__.py
+++ b/hyperkitty/lib/__init__.py
@@ -25,7 +25,6 @@ import datetime
from django.conf import settings
-from hyperkitty.models import Rating
def gravatar_url(email):
@@ -88,18 +87,3 @@ def get_display_dates(year, month, day):
end_date = begin_date + datetime.timedelta(days=1)
return begin_date, end_date
-
-
-def get_votes(message_id_hash):
- """Extract all the votes for this message"""
- likes = dislikes = 0
- try:
- votes = Rating.objects.filter(messageid=message_id_hash)
- except Rating.DoesNotExist:
- votes = {}
- for vote in votes:
- if vote.vote == 1:
- likes += 1
- elif vote.vote == -1:
- dislikes += 1
- return likes, dislikes
diff --git a/hyperkitty/lib/voting.py b/hyperkitty/lib/voting.py
new file mode 100644
index 0000000..7d7cc64
--- /dev/null
+++ b/hyperkitty/lib/voting.py
@@ -0,0 +1,54 @@
+#-*- coding: utf-8 -*-
+# Copyright (C) 1998-2012 by the Free Software Foundation, Inc.
+#
+# This file is part of HyperKitty.
+#
+# HyperKitty is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# HyperKitty is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# HyperKitty. If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Aurelien Bompard <abompard@fedoraproject.org>
+#
+
+
+from hyperkitty.models import Rating
+
+
+def get_votes(message_id_hash, user=None):
+ """Extract all the votes for this message"""
+ likes = dislikes = 0
+ try:
+ votes = Rating.objects.filter(messageid=message_id_hash)
+ except Rating.DoesNotExist:
+ votes = {}
+ myvote = 0
+ for vote in votes:
+ if vote.vote == 1:
+ likes += 1
+ elif vote.vote == -1:
+ dislikes += 1
+ if user is not None and user.is_authenticated() and vote.user == user:
+ myvote = vote.vote
+ return likes, dislikes, myvote
+
+
+def set_message_votes(message, user=None):
+ # Extract all the votes for this message
+ message.likes, message.dislikes, message.myvote = \
+ get_votes(message.message_id_hash, user)
+ message.likestatus = "neutral"
+ if message.likes - message.dislikes >= 10:
+ message.likestatus = "likealot"
+ elif message.likes - message.dislikes > 0:
+ message.likestatus = "like"
+ #elif message.likes - message.dislikes < 0:
+ # message.likestatus = "dislike"
diff --git a/hyperkitty/static/js/hyperkitty.js b/hyperkitty/static/js/hyperkitty.js
index 33aa2a6..d96dfb0 100644
--- a/hyperkitty/static/js/hyperkitty.js
+++ b/hyperkitty/static/js/hyperkitty.js
@@ -39,30 +39,32 @@ function form_to_json(form) {
function vote(elem, value) {
if ($(elem).hasClass("disabled")) {
- return;
+ return false;
}
- var data = form_to_json($(elem).parent("form"));
+ var form = $(elem).parents("form").first();
+ var data = form_to_json(form);
data['vote'] = value;
$.ajax({
type: "POST",
- url: $(elem).parent("form").attr("action"),
+ url: form.attr("action"),
dataType: "json",
data: data,
success: function(response) {
- var likestatus = $(elem).parent("form").find(".likestatus");
- likestatus.find(".likecount").html(response.like);
- likestatus.find(".dislikecount").html(response.dislike);
+ form.replaceWith(response.html);
},
error: function(jqXHR, textStatus, errorThrown) {
alert(jqXHR.responseText);
}
});
+ return false;
}
function setup_vote() {
+ /*
$("a.youlike").click(function(e) { e.preventDefault(); vote(this, 1); });
$("a.youdislike").click(function(e) { e.preventDefault(); vote(this, -1); });
+ */
}
diff --git a/hyperkitty/templates/messages/like_form.html b/hyperkitty/templates/messages/like_form.html
index 7896fed..867a8a4 100644
--- a/hyperkitty/templates/messages/like_form.html
+++ b/hyperkitty/templates/messages/like_form.html
@@ -1,11 +1,19 @@
<form method="post" class="likeform"
action="{% url message_vote mlist_fqdn=mlist.name, message_id_hash=message_id_hash %}">
{% csrf_token %}
- <span class="likestatus {{ object.likestatus }}">
- +<span class="likecount">{{ object.likes }}</span>/-<span class="dislikecount">{{ object.dislikes }}</span>
- </span>
- <a class="youlike{% if not user.is_authenticated %} disabled" title="You must be logged-in to vote.{% endif %}" href="#like">Like</a>
- <a class="youdislike{% if not user.is_authenticated %} disabled" title="You must be logged-in to vote.{% endif %}" href="#dislike">Dislike</a>
+ <span class="likestatus {{ object.likestatus }}">+{{ object.likes }}/-{{ object.dislikes }}</span>
+ {% if object.myvote == 1 %}
+ <span class="youlike">You like it
+ (<a href="#cancelvote" onclick="vote(this, 0)">cancel</a>)</span>
+ {% elif object.myvote == -1 %}
+ <span class="youdislike">You dislike it
+ (<a href="#cancelvote" onclick="vote(this, 0)">cancel</a>)</span>
+ {% else %}
+ <a class="youlike{% if not user.is_authenticated %} disabled" title="You must be logged-in to vote.{% endif %}"
+ href="#like" onclick="vote(this, 1)">Like</a>
+ <a class="youdislike{% if not user.is_authenticated %} disabled" title="You must be logged-in to vote.{% endif %}"
+ href="#dislike" onclick="vote(this, -1)">Dislike</a>
+ {% endif %}
</form>
{# vim: set noet: #}
diff --git a/hyperkitty/tests/test_views.py b/hyperkitty/tests/test_views.py
index b4d897f..e29d90b 100644
--- a/hyperkitty/tests/test_views.py
+++ b/hyperkitty/tests/test_views.py
@@ -25,14 +25,18 @@ import urllib
from mock import Mock, patch
+import django.utils.simplejson as json
from django.test import TestCase
from django.test.client import Client, RequestFactory
-from django.contrib.auth.models import User
+from django.test.utils import override_settings
+from django.contrib.auth.models import User, AnonymousUser
from django.core.urlresolvers import reverse
-#from hyperkitty.views.list import archives
+from hyperkitty.models import Rating
+
+@override_settings(USE_SSL=False, USE_INTERNAL_AUTH=True)
class AccountViewsTestCase(TestCase):
def setUp(self):
@@ -44,7 +48,7 @@ class AccountViewsTestCase(TestCase):
self.assertRedirects(response, "%s?next=%s" % (reverse('user_login'), reverse('user_profile')))
def test_profile(self):
- User.objects.create_user('testuser', 'syst3m.w0rm+test@gmail.com', 'testPass')
+ User.objects.create_user('testuser', 'test@example.com', 'testPass')
user = self.client.login(username='testuser', password='testPass')
response = self.client.get(reverse('user_profile'))
@@ -58,13 +62,13 @@ class AccountViewsTestCase(TestCase):
def test_registration(self):
-
- User.objects.create_user('testuser', 'syst3m.w0rm+test@gmail.com', 'testPass')
+ User.objects.create_user('testuser', 'test@example.com', 'testPass')
user = self.client.login(username='testuser', password='testPass')
- # If the user if already logged in, redirect to index page..don't let him register again
+ # If the user if already logged in, redirect to index page...
+ # Don't let him register again
response = self.client.get(reverse('user_registration'))
- self.assertRedirects(response, reverse('index'))
+ self.assertRedirects(response, reverse('root'))
self.client.logout()
# Access the user registration page after logging out and try to register now
@@ -74,23 +78,83 @@ class AccountViewsTestCase(TestCase):
# @TODO: Try to register a user and verify its working
-class MessageViewsTestCase(TestCase):
- def setUp(self):
- self.client = Client()
+from hyperkitty.views.message import vote
- def test_good_vote(self):
- User.objects.create_user('testuser', 'syst3m.w0rm+test@gmail.com', 'testPass')
- user = self.client.login(username='testuser', password='testPass')
+class MessageViewsTestCase(TestCase):
- resp = self.client.post(reverse('message_vote', kwargs={'mlist_fqdn': 'list@list.com'}), {'vote': 1, 'message_id_hash': 123, })
+ def setUp(self):
+ self.user = User.objects.create_user(
+ 'testuser', 'test@example.com', 'testPass')
+ # Fake KittStore
+ class FakeMessage(object):
+ def __init__(self, h):
+ self.message_id_hash = h
+ self.store = Mock()
+ self.store.get_message_by_hash_from_list.side_effect = \
+ lambda l, h: FakeMessage(h)
+ defaults = {"kittystore.store": self.store}
+ self.factory = RequestFactory(**defaults)
+
+
+ def test_vote_up(self):
+ request = self.factory.post("/vote", {"vote": "1"})
+ request.user = self.user
+ resp = vote(request, 'list@example.com', '123')
self.assertEqual(resp.status_code, 200)
-
- def test_unauth_vote(self):
- resp = self.client.post(reverse('message_vote', kwargs={'mlist_fqdn': 'list@list.com'}), {'vote': 1, 'message_id_hash': 123, })
+ v = Rating.objects.get(user=self.user, messageid="123",
+ list_address='list@example.com')
+ self.assertEqual(v.vote, 1)
+ result = json.loads(resp.content)
+ self.assertEqual(result["like"], 1)
+ self.assertEqual(result["dislike"], 0)
+
+
+ def test_vote_down(self):
+ request = self.factory.post("/vote", {"vote": "-1"})
+ request.user = self.user
+ resp = vote(request, 'list@example.com', '123')
+ self.assertEqual(resp.status_code, 200)
+ v = Rating.objects.get(user=self.user, messageid="123",
+ list_address='list@example.com')
+ self.assertEqual(v.vote, -1)
+ result = json.loads(resp.content)
+ self.assertEqual(result["like"], 0)
+ self.assertEqual(result["dislike"], 1)
+
+
+ def test_vote_cancel(self):
+ v = Rating(list_address="list@example.com", messageid="m1", vote=1)
+ v.user = self.user
+ v.save()
+ v = Rating(list_address="list@example.com", messageid="m2", vote=-1)
+ v.user = self.user
+ v.save()
+ for msg in ["m1", "m2"]:
+ request = self.factory.post("/vote", {"vote": "0"})
+ request.user = self.user
+ resp = vote(request, 'list@example.com', msg)
+ self.assertEqual(resp.status_code, 200)
+ try:
+ Rating.objects.get(user=self.user, messageid=msg,
+ list_address='list@example.com')
+ except Rating.DoesNotExist:
+ pass
+ else:
+ self.fail("Vote for msg %s should have been deleted" % msg)
+ result = json.loads(resp.content)
+ self.assertEqual(result["like"], 0)
+ self.assertEqual(result["dislike"], 0)
+
+
+ def test_unauth_vote(self):
+ request = self.factory.post("/vote", {"vote": "1"})
+ request.user = AnonymousUser()
+ resp = vote(request, 'list@example.com', '123')
self.assertEqual(resp.status_code, 403)
+
from hyperkitty.views.list import archives
class ListArchivesTestCase(TestCase):
diff --git a/hyperkitty/views/list.py b/hyperkitty/views/list.py
index 7f89ee8..7dbb1ad 100644
--- a/hyperkitty/views/list.py
+++ b/hyperkitty/views/list.py
@@ -29,7 +29,8 @@ from django.core.urlresolvers import reverse
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from hyperkitty.models import Tag, Favorite
-from hyperkitty.lib import get_months, get_store, get_display_dates, get_votes
+from hyperkitty.lib import get_months, get_store, get_display_dates
+from hyperkitty.lib.voting import get_votes
from forms import SearchForm
@@ -77,10 +78,13 @@ def _thread_list(request, mlist, threads, template_name='thread_list.html', extr
totallikes = 0
totaldislikes = 0
for message_id_hash in thread.email_id_hashes:
- likes, dislikes = get_votes(message_id_hash)
+ likes, dislikes, myvote = get_votes(message_id_hash, request.user)
totallikes = totallikes + likes
totalvotes = totalvotes + likes + dislikes
totaldislikes = totaldislikes + dislikes
+ if message_id_hash == thread.thread_id:
+ # Starting email: same id as the thread_id
+ thread.myvote = myvote
try:
thread.likes = totallikes / totalvotes
except ZeroDivisionError:
diff --git a/hyperkitty/views/message.py b/hyperkitty/views/message.py
index 0245b24..32467f9 100644
--- a/hyperkitty/views/message.py
+++ b/hyperkitty/views/message.py
@@ -22,6 +22,7 @@
import urllib
import datetime
+from collections import namedtuple
import django.utils.simplejson as json
from django.http import HttpResponse, Http404
@@ -30,9 +31,11 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from django.core.mail import EmailMessage
from django.core.exceptions import SuspiciousOperation
+from django.template import RequestContext, loader
from django.contrib.auth.decorators import login_required
-from hyperkitty.lib import get_store, get_months, get_votes
+from hyperkitty.lib import get_store, get_months
+from hyperkitty.lib.voting import set_message_votes
from hyperkitty.models import Rating
from forms import SearchForm, ReplyForm, PostForm
@@ -48,17 +51,7 @@ def index(request, mlist_fqdn, message_id_hash):
if message is None:
raise Http404
message.sender_email = message.sender_email.strip()
-
- # Extract all the votes for this message
- message.likes, message.dislikes = get_votes(message_id_hash)
- message.likestatus = "neutral"
- if message.likes - message.dislikes >= 10:
- message.likestatus = "likealot"
- elif message.likes - message.dislikes > 0:
- message.likestatus = "like"
- #elif message.likes - message.dislikes < 0:
- # message.likestatus = "dislike"
-
+ set_message_votes(message, request.user)
mlist = store.get_list(mlist_fqdn)
context = {
@@ -98,11 +91,21 @@ def attachment(request, mlist_fqdn, message_id_hash, counter, filename):
def vote(request, mlist_fqdn, message_id_hash):
""" Add a rating to a given message identified by messageid. """
+ if request.method != 'POST':
+ raise SuspiciousOperation
+
if not request.user.is_authenticated():
return HttpResponse('You must be logged in to vote',
content_type="text/plain", status=403)
+ store = get_store(request)
+ message = store.get_message_by_hash_from_list(mlist_fqdn, message_id_hash)
+ if message is None:
+ raise Http404
+
value = int(request.POST['vote'])
+ if value not in [-1, 0, 1]:
+ raise SuspiciousOperation
# Checks if the user has already voted for a this message.
try:
@@ -112,21 +115,33 @@ def vote(request, mlist_fqdn, message_id_hash):
return HttpResponse("You've already cast this vote",
content_type="text/plain", status=403)
except Rating.DoesNotExist:
- v = Rating(list_address=mlist_fqdn, messageid=message_id_hash, vote=value)
- v.user = request.user
-
- v.vote = value
- v.save()
+ if value != 0:
+ v = Rating(list_address=mlist_fqdn, messageid=message_id_hash,
+ vote=value)
+ v.user = request.user
+ else:
+ return HttpResponse("There is no vote to cancel",
+ content_type="text/plain", status=500)
+
+ if value == 0:
+ v.delete()
+ else:
+ v.vote = value
+ v.save()
# Extract all the votes for this message to refresh it
- status = { "like": 0, "dislike": 0 }
- for vote in Rating.objects.filter(messageid=message_id_hash):
- if vote.vote == 1:
- status["like"] += 1
- elif vote.vote == -1:
- status["dislike"] += 1
-
- return HttpResponse(json.dumps(status),
+ set_message_votes(message, request.user)
+ FakeMList = namedtuple("MailingList", ["name"])
+ t = loader.get_template('messages/like_form.html')
+ html = t.render(RequestContext(request, {
+ "object": message,
+ "message_id_hash": message_id_hash,
+ "mlist": FakeMList(name=mlist_fqdn)}
+ ))
+
+ result = { "like": message.likes, "dislike": message.dislikes,
+ "html": html, }
+ return HttpResponse(json.dumps(result),
mimetype='application/javascript')
diff --git a/hyperkitty/views/thread.py b/hyperkitty/views/thread.py
index 8fcca20..a1788cd 100644
--- a/hyperkitty/views/thread.py
+++ b/hyperkitty/views/thread.py
@@ -21,6 +21,7 @@
#
import datetime
+from collections import namedtuple
import django.utils.simplejson as json
@@ -33,7 +34,8 @@ from django.core.exceptions import SuspiciousOperation
from hyperkitty.models import Tag, Favorite
from forms import SearchForm, AddTagForm, ReplyForm
-from hyperkitty.lib import get_months, get_store, stripped_subject, get_votes
+from hyperkitty.lib import get_months, get_store, stripped_subject
+from hyperkitty.lib.voting import set_message_votes
def thread_index(request, mlist_fqdn, threadid, month=None, year=None):
@@ -55,15 +57,7 @@ def thread_index(request, mlist_fqdn, threadid, month=None, year=None):
participants = {}
for email in emails:
# Extract all the votes for this message
- email.likes, email.dislikes = get_votes(email.message_id_hash)
- email.likestatus = "neutral"
- if email.likes - email.dislikes >= 10:
- email.likestatus = "likealot"
- elif email.likes - email.dislikes > 0:
- email.likestatus = "like"
- #elif email.likes - email.dislikes < 0:
- # email.likestatus = "dislike"
-
+ set_message_votes(email, request.user)
# Statistics on how many participants and messages this month
participants[email.sender_name] = email.sender_email
@@ -146,10 +140,11 @@ def add_tag(request, mlist_fqdn, threadid):
# Now refresh the tag list
tags = Tag.objects.filter(threadid=threadid, list_address=mlist_fqdn)
+ FakeMList = namedtuple("MailingList", ["name"])
t = loader.get_template('threads/tags.html')
html = t.render(RequestContext(request, {
"tags": tags,
- "list_address": mlist_fqdn}))
+ "mlist": FakeMList(name=mlist_fqdn)}))
response = {"tags": [ t.tag for t in tags ], "html": html}
return HttpResponse(json.dumps(response),