diff --git a/proposals/models.py b/proposals/models.py
index 622f301..ade8e9b 100644
--- a/proposals/models.py
+++ b/proposals/models.py
@@ -13,6 +13,8 @@ from sortedm2m.fields import SortedManyToManyField
from accounts.models import Participation
from ponyconf.utils import PonyConfModel, enum_to_choices
+from .utils import query_sum
+
__all__ = ['Topic', 'Talk']
@@ -65,6 +67,9 @@ class Talk(PonyConfModel):
def is_editable_by(self, user):
return user == self.proposer or user in self.speakers.all() or self.is_moderable_by(user)
+ def score(self):
+ return query_sum(self.vote_set, 'vote')
+
class Vote(PonyConfModel):
@@ -74,3 +79,9 @@ class Vote(PonyConfModel):
class Meta:
unique_together = ('talk', 'user')
+
+ def __str__(self):
+ return "%+i by %s for %s" % (self.vote, self.user, self.talk)
+
+ def get_absolute_url(self):
+ return self.talk.get_absolute_url()
diff --git a/proposals/templates/proposals/talk_detail.html b/proposals/templates/proposals/talk_detail.html
index 6afe041..060e32f 100644
--- a/proposals/templates/proposals/talk_detail.html
+++ b/proposals/templates/proposals/talk_detail.html
@@ -12,11 +12,11 @@
{{ talk.get_event_display }}
-Description:
+Description:
{{ talk.description }}
-Speakers:
+Speakers:
{% for speaker in talk.speakers.all %}
@@ -26,7 +26,7 @@
{% endfor %}
-Topics:
+Topics:
{% for topic in talk.topics.all %}
@@ -37,7 +37,18 @@
{% if moderate_perm %}
Moderation
-Messages:
+Vote:
+
+
+Sum: {{ talk.score }}
+
+Messages:
{% for message in talk.conversation.messages.all %}
{% include 'conversations/_message_detail.html' %}
{% endfor %}
diff --git a/proposals/tests.py b/proposals/tests.py
index e9a2289..6139a9c 100644
--- a/proposals/tests.py
+++ b/proposals/tests.py
@@ -5,7 +5,7 @@ from django.test import TestCase
from accounts.models import Participation
-from .models import Talk, Topic
+from .models import Talk, Topic, Vote
class ProposalsTests(TestCase):
@@ -43,17 +43,26 @@ class ProposalsTests(TestCase):
{'title': 'mega talk', 'description': 'mega', 'event': 1}).status_code, 403)
self.assertEqual(self.client.get(reverse('list-talks')).status_code, 200)
+ # Vote
+ self.assertEqual(talk.score(), 0)
+ self.assertEqual(self.client.get(reverse('vote', kwargs={'talk': talk.slug, 'score': 2})).status_code, 403)
+ self.client.login(username='c', password='c')
+ self.assertEqual(self.client.get(reverse('vote', kwargs={'talk': talk.slug, 'score': 2})).status_code, 302)
+ self.assertEqual(talk.score(), 2)
+
# Models str & get_asbolute_url
- for model in [Talk, Topic]:
+ for model in [Talk, Topic, Vote]:
item = model.objects.first()
self.assertEqual(self.client.get(item.get_absolute_url()).status_code, 200)
self.assertTrue(str(item))
- # Talk.is_editable_by
+ # Talk.is_{editable,moderable}_by
a, b, c = User.objects.all()
- self.assertTrue(talk.is_editable_by(c))
+ self.assertTrue(talk.is_moderable_by(c))
self.assertFalse(talk.is_editable_by(b))
+ self.assertFalse(talk.is_moderable_by(b))
self.client.login(username='a', password='a')
self.client.post(reverse('edit-talk', kwargs={'talk': 'super-talk'}),
{'title': 'mega talk', 'description': 'mega', 'event': 1, 'speakers': "2,1"})
self.assertTrue(talk.is_editable_by(b))
+ self.assertFalse(talk.is_moderable_by(b))
diff --git a/proposals/urls.py b/proposals/urls.py
index b930945..2badff1 100644
--- a/proposals/urls.py
+++ b/proposals/urls.py
@@ -7,6 +7,7 @@ urlpatterns = [
url(r'^talk/$', views.talk_list, name='list-talks'),
url(r'^talk/add/$', views.talk_edit, name='add-talk'),
url(r'^talk/edit/(?P[-\w]+)$', views.talk_edit, name='edit-talk'),
+ url(r'^talk/vote/(?P[-\w]+)/(?P[-0-2]+)$', views.vote, name='vote'),
url(r'^talk/details/(?P[-\w]+)$', views.TalkDetail.as_view(), name='show-talk'),
url(r'^talk/by-topic/(?P[-\w]+)$', views.talk_list_by_topic, name='list-talks-by-topic'),
url(r'^talk/by-speaker/(?P[\w.@+-]+)$', views.talk_list_by_speaker, name='list-talks-by-speaker'),
diff --git a/proposals/utils.py b/proposals/utils.py
new file mode 100644
index 0000000..a8b12eb
--- /dev/null
+++ b/proposals/utils.py
@@ -0,0 +1,6 @@
+from django.db.models import Sum
+from django.db.models.functions import Coalesce
+
+
+def query_sum(queryset, field):
+ return queryset.aggregate(s=Coalesce(Sum(field), 0))['s']
diff --git a/proposals/views.py b/proposals/views.py
index ede1771..2c41acd 100644
--- a/proposals/views.py
+++ b/proposals/views.py
@@ -12,7 +12,7 @@ from accounts.mixins import StaffRequiredMixin
from accounts.models import Participation
from .forms import TalkForm
-from .models import Talk, Topic
+from .models import Talk, Topic, Vote
from .signals import new_talk
@@ -81,11 +81,15 @@ def talk_edit(request, talk=None):
class TalkDetail(LoginRequiredMixin, DetailView):
queryset = Talk.on_site.all()
- def get_context_data(self, **kwargs):
- kwargs['edit_perm'] = self.object.is_editable_by(self.request.user)
- kwargs['moderate_perm'] = self.object.is_moderable_by(self.request.user)
- kwargs['form_url'] = reverse('talk-conversation', kwargs={'talk': self.object.slug})
- return super().get_context_data(**kwargs)
+ def get_context_data(self, **ctx):
+ user = self.request.user
+ if self.object.is_moderable_by(user):
+ vote = Vote.objects.filter(talk=self.object, user=Participation.on_site.get(user=user)).first()
+ ctx.update(edit_perm=True, moderate_perm=True, vote=vote,
+ form_url=reverse('talk-conversation', kwargs={'talk': self.object.slug}))
+ else:
+ ctx['edit_perm'] = self.object.is_editable_by(user)
+ return super().get_context_data(**ctx)
class TopicList(LoginRequiredMixin, ListView):
@@ -102,6 +106,20 @@ class SpeakerList(StaffRequiredMixin, ListView):
template_name = 'proposals/speaker_list.html'
+@login_required
+def vote(request, talk, score):
+ site = get_current_site(request)
+ talk = get_object_or_404(Talk, site=site, slug=talk)
+ user = Participation.on_site.get(user=request.user)
+ if not talk.is_moderable_by(request.user):
+ raise PermissionDenied()
+ vote, created = Vote.objects.get_or_create(talk=talk, user=user)
+ vote.vote = int(score)
+ vote.save()
+ messages.success(request, "Vote successfully %s" % ('created' if created else 'updated'))
+ return redirect(talk.get_absolute_url())
+
+
@login_required
def user_details(request, username):
return render(request, 'proposals/user_details.html', {