Service for adding, removing and listing votes
parent
baeab00e28
commit
fcf4747e93
|
@ -183,6 +183,7 @@ INSTALLED_APPS = [
|
|||
"taiga.projects.history",
|
||||
"taiga.projects.notifications",
|
||||
"taiga.projects.stars",
|
||||
"taiga.projects.votes",
|
||||
|
||||
"south",
|
||||
"reversion",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
|
@ -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
|
|
@ -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))
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
|
@ -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")
|
||||
|
|
|
@ -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]
|
Loading…
Reference in New Issue