diff options
-rw-r--r-- | hyperkitty/lib/__init__.py | 16 | ||||
-rw-r--r-- | hyperkitty/lib/voting.py | 54 | ||||
-rw-r--r-- | hyperkitty/static/js/hyperkitty.js | 14 | ||||
-rw-r--r-- | hyperkitty/templates/messages/like_form.html | 18 | ||||
-rw-r--r-- | hyperkitty/tests/test_views.py | 98 | ||||
-rw-r--r-- | hyperkitty/views/list.py | 8 | ||||
-rw-r--r-- | hyperkitty/views/message.py | 65 | ||||
-rw-r--r-- | hyperkitty/views/thread.py | 17 |
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), |