Service for adding, removing and listing votes

remotes/origin/enhancement/email-actions
Anler Hp 2014-05-30 12:43:34 +02:00
parent baeab00e28
commit fcf4747e93
9 changed files with 225 additions and 0 deletions

View File

@ -183,6 +183,7 @@ INSTALLED_APPS = [
"taiga.projects.history",
"taiga.projects.notifications",
"taiga.projects.stars",
"taiga.projects.votes",
"south",
"reversion",

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,35 @@
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes import generic
class Votes(models.Model):
content_type = models.ForeignKey("contenttypes.ContentType")
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey("content_type", "object_id")
count = models.PositiveIntegerField(default=0)
class Meta:
verbose_name = _("Votes")
verbose_name_plural = _("Votes")
unique_together = ("content_type", "object_id")
def __str__(self):
return self.count
class Vote(models.Model):
content_type = models.ForeignKey("contenttypes.ContentType")
object_id = models.PositiveIntegerField(null=False)
content_object = generic.GenericForeignKey("content_type", "object_id")
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
related_name="votes", verbose_name=_("votes"))
class Meta:
verbose_name = _("Vote")
verbose_name_plural = _("Votes")
unique_together = ("content_type", "object_id", "user")
def __str__(self):
return self.user

View File

@ -0,0 +1,92 @@
from django.db.models import F
from django.db.transaction import atomic
from django.db.models.loading import get_model
from django.contrib.auth import get_user_model
from .models import Votes, Vote
def add_vote(obj, user):
"""Add a vote to an object.
If the user has already voted the object nothing happends, so this function can be considered
idempotent.
:param obj: Any Django model instance.
:param user: User adding the vote. :class:`~taiga.users.models.User` instance.
"""
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
with atomic():
_, created = Vote.objects.get_or_create(content_type=obj_type, object_id=obj.id, user=user)
if not created:
return
votes, _ = Votes.objects.get_or_create(content_type=obj_type, object_id=obj.id)
votes.count = F('count') + 1
votes.save()
def remove_vote(obj, user):
"""Remove an user vote from an object.
If the user has not voted the object nothing happens so this function can be considered
idempotent.
:param obj: Any Django model instance.
:param user: User removing her vote. :class:`~taiga.users.models.User` instance.
"""
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
with atomic():
qs = Vote.objects.filter(content_type=obj_type, object_id=obj.id, user=user)
if not qs.exists():
return
qs.delete()
votes, _ = Votes.objects.get_or_create(content_type=obj_type, object_id=obj.id)
votes.count = F('count') - 1
votes.save()
def get_voters(obj):
"""Get the voters of an object.
:param obj: Any Django model instance.
:return: User queryset object representing the users that voted the object.
"""
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
return get_user_model().objects.filter(votes__content_type=obj_type, votes__object_id=obj.id)
def get_votes(obj):
"""Get the number of votes an object has.
:param obj: Any Django model instance.
:return: Number of votes or `0` if the object has no votes at all.
"""
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj)
try:
return Votes.objects.get(content_type=obj_type, object_id=obj.id).count
except Votes.DoesNotExist:
return 0
def get_voted(user, obj_class):
"""Get the objects voted by an user.
:param user: :class:`~taiga.users.models.User` instance.
:param obj_class: Show only objects of this kind. Can be any Django model class.
:return:
"""
obj_type = get_model("contenttypes", "ContentType").objects.get_for_model(obj_class)
conditions = ('votes_vote.content_type_id = %s',
'%s.id = votes_vote.object_id' % obj_class._meta.db_table,
'votes_vote.user_id = %s')
return obj_class.objects.extra(where=conditions, tables=('votes_vote',),
params=(obj_type.id, user.id))

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@ -173,3 +173,22 @@ class StarsFactory(Factory):
project = factory.SubFactory("tests.factories.ProjectFactory")
count = 0
class VoteFactory(Factory):
FACTORY_FOR = get_model("votes", "Vote")
content_type = factory.SubFactory("tests.factories.ContentTypeFactory")
object_id = factory.Sequence(lambda n: n)
user = factory.SubFactory("tests.factories.UserFactory")
class VotesFactory(Factory):
FACTORY_FOR = get_model("votes", "Votes")
content_type = factory.SubFactory("tests.factories.ContentTypeFactory")
object_id = factory.Sequence(lambda n: n)
class ContentTypeFactory(Factory):
FACTORY_FOR = get_model("contenttypes", "ContentType")

View File

@ -0,0 +1,69 @@
import pytest
from django.contrib.contenttypes.models import ContentType
from taiga.projects.votes import services as votes, models
from .. import factories as f
pytestmark = pytest.mark.django_db
def test_add_vote():
project = f.ProjectFactory()
project_type = ContentType.objects.get_for_model(project)
user = f.UserFactory()
votes_qs = models.Votes.objects.filter(content_type=project_type, object_id=project.id)
votes.add_vote(project, user)
assert votes_qs.get().count == 1
votes.add_vote(project, user) # add_vote must be idempotent
assert votes_qs.get().count == 1
def test_remove_vote():
user = f.UserFactory()
project = f.ProjectFactory()
project_type = ContentType.objects.get_for_model(project)
votes_qs = models.Votes.objects.filter(content_type=project_type, object_id=project.id)
f.VotesFactory(content_type=project_type, object_id=project.id, count=1)
f.VoteFactory(content_type=project_type, object_id=project.id, user=user)
assert votes_qs.get().count == 1
votes.remove_vote(project, user)
assert votes_qs.get().count == 0
votes.remove_vote(project, user) # remove_vote must be idempotent
assert votes_qs.get().count == 0
def test_get_votes():
project = f.ProjectFactory()
project_type = ContentType.objects.get_for_model(project)
f.VotesFactory(content_type=project_type, object_id=project.id, count=4)
assert votes.get_votes(project) == 4
def test_get_voters():
f.UserFactory()
project = f.ProjectFactory()
project_type = ContentType.objects.get_for_model(project)
vote = f.VoteFactory(content_type=project_type, object_id=project.id)
assert list(votes.get_voters(project)) == [vote.user]
def test_get_voted():
f.ProjectFactory()
project = f.ProjectFactory()
project_type = ContentType.objects.get_for_model(project)
vote = f.VoteFactory(content_type=project_type, object_id=project.id)
assert list(votes.get_voted(vote.user, type(project))) == [project]