Improve votes module

remotes/origin/enhancement/email-actions
David Barragán Merino 2015-08-05 22:22:05 +02:00
parent cf63031844
commit 44eee5212a
32 changed files with 975 additions and 320 deletions

View File

@ -4,13 +4,15 @@
## 1.9.0 ??? (unreleased) ## 1.9.0 ??? (unreleased)
### Features ### Features
- Add a "field type" property for custom fields: 'text' and 'multiline text' right now (thanks to [@artlepool](https://github.com/artlepool)) - Add a "field type" property for custom fields: 'text' and 'multiline text' right now (thanks to [@artlepool](https://github.com/artlepool)).
- Allow multiple actions in the commit messages. - Allow multiple actions in the commit messages.
- Now every user that coments USs, Issues or Tasks will be involved in it (add author to the watchers list). - Now every user that coments USs, Issues or Tasks will be involved in it (add author to the watchers list).
- Fix the compatibility with BitBucket webhooks and add issues and issues comments integration. - Fix the compatibility with BitBucket webhooks and add issues and issues comments integration.
- Add custom videoconference system. - Add custom videoconference system.
- Add support for comments in the Gitlab webhooks integration. - Add support for comments in the Gitlab webhooks integration.
- Now profile timelines only show content about the objects (US/Tasks/Issues/Wiki pages) you are involved. - Now profile timelines only show content about the objects (US/Tasks/Issues/Wiki pages) you are involved.
- US, tasks and Issues can be upvoted or downvoted and the voters list can be obtained.
- Project can be starred or unstarred and the fans list can be obtained.
- i18n. - i18n.
- Add polish (pl) translation. - Add polish (pl) translation.
- Add portuguese (Brazil) (pt_BR) translation. - Add portuguese (Brazil) (pt_BR) translation.

View File

@ -32,7 +32,6 @@ USER_PERMISSIONS = [
('view_milestones', _('View milestones')), ('view_milestones', _('View milestones')),
('view_us', _('View user stories')), ('view_us', _('View user stories')),
('view_issues', _('View issues')), ('view_issues', _('View issues')),
('vote_issues', _('Vote issues')),
('view_tasks', _('View tasks')), ('view_tasks', _('View tasks')),
('view_wiki_pages', _('View wiki pages')), ('view_wiki_pages', _('View wiki pages')),
('view_wiki_links', _('View wiki links')), ('view_wiki_links', _('View wiki links')),
@ -41,15 +40,20 @@ USER_PERMISSIONS = [
('add_comments_to_us', _('Add comments to user stories')), ('add_comments_to_us', _('Add comments to user stories')),
('add_comments_to_task', _('Add comments to tasks')), ('add_comments_to_task', _('Add comments to tasks')),
('add_issue', _('Add issues')), ('add_issue', _('Add issues')),
('add_comments_issue', _('Add comments to issues')), ('add_comments_to_issue', _('Add comments to issues')),
('add_wiki_page', _('Add wiki page')), ('add_wiki_page', _('Add wiki page')),
('modify_wiki_page', _('Modify wiki page')), ('modify_wiki_page', _('Modify wiki page')),
('add_wiki_link', _('Add wiki link')), ('add_wiki_link', _('Add wiki link')),
('modify_wiki_link', _('Modify wiki link')), ('modify_wiki_link', _('Modify wiki link')),
('star_project', _('Star project')),
('vote_us', _('Vote user story')),
('vote_task', _('Vote task')),
('vote_issue', _('Vote issue')),
] ]
MEMBERS_PERMISSIONS = [ MEMBERS_PERMISSIONS = [
('view_project', _('View project')), ('view_project', _('View project')),
('star_project', _('Star project')),
# Milestone permissions # Milestone permissions
('view_milestones', _('View milestones')), ('view_milestones', _('View milestones')),
('add_milestone', _('Add milestone')), ('add_milestone', _('Add milestone')),
@ -60,17 +64,19 @@ MEMBERS_PERMISSIONS = [
('add_us', _('Add user story')), ('add_us', _('Add user story')),
('modify_us', _('Modify user story')), ('modify_us', _('Modify user story')),
('delete_us', _('Delete user story')), ('delete_us', _('Delete user story')),
('vote_us', _('Vote user story')),
# Task permissions # Task permissions
('view_tasks', _('View tasks')), ('view_tasks', _('View tasks')),
('add_task', _('Add task')), ('add_task', _('Add task')),
('modify_task', _('Modify task')), ('modify_task', _('Modify task')),
('delete_task', _('Delete task')), ('delete_task', _('Delete task')),
('vote_task', _('Vote task')),
# Issue permissions # Issue permissions
('view_issues', _('View issues')), ('view_issues', _('View issues')),
('vote_issues', _('Vote issues')),
('add_issue', _('Add issue')), ('add_issue', _('Add issue')),
('modify_issue', _('Modify issue')), ('modify_issue', _('Modify issue')),
('delete_issue', _('Delete issue')), ('delete_issue', _('Delete issue')),
('vote_issue', _('Vote issue')),
# Wiki page permissions # Wiki page permissions
('view_wiki_pages', _('View wiki pages')), ('view_wiki_pages', _('View wiki pages')),
('add_wiki_page', _('Add wiki page')), ('add_wiki_page', _('Add wiki page')),

View File

@ -44,15 +44,14 @@ from . import models
from . import permissions from . import permissions
from . import services from . import services
from .votes import serializers as votes_serializers from .votes.mixins.viewsets import StarredResourceMixin, VotersViewSetMixin
from .votes import services as votes_service
from .votes.utils import attach_votescount_to_queryset
###################################################### ######################################################
## Project ## Project
###################################################### ######################################################
class ProjectViewSet(HistoryResourceMixin, ModelCrudViewSet): class ProjectViewSet(StarredResourceMixin, HistoryResourceMixin, ModelCrudViewSet):
queryset = models.Project.objects.all()
serializer_class = serializers.ProjectDetailSerializer serializer_class = serializers.ProjectDetailSerializer
admin_serializer_class = serializers.ProjectDetailAdminSerializer admin_serializer_class = serializers.ProjectDetailAdminSerializer
list_serializer_class = serializers.ProjectSerializer list_serializer_class = serializers.ProjectSerializer
@ -61,6 +60,10 @@ class ProjectViewSet(HistoryResourceMixin, ModelCrudViewSet):
filter_fields = (('member', 'members'),) filter_fields = (('member', 'members'),)
order_by_fields = ("memberships__user_order",) order_by_fields = ("memberships__user_order",)
def get_queryset(self):
qs = super().get_queryset()
return self.attach_votes_attrs_to_queryset(qs)
@list_route(methods=["POST"]) @list_route(methods=["POST"])
def bulk_update_order(self, request, **kwargs): def bulk_update_order(self, request, **kwargs):
if self.request.user.is_anonymous(): if self.request.user.is_anonymous():
@ -74,10 +77,6 @@ class ProjectViewSet(HistoryResourceMixin, ModelCrudViewSet):
services.update_projects_order_in_bulk(data, "user_order", request.user) services.update_projects_order_in_bulk(data, "user_order", request.user)
return response.NoContent(data=None) return response.NoContent(data=None)
def get_queryset(self):
qs = models.Project.objects.all()
return attach_votescount_to_queryset(qs, as_field="stars_count")
def get_serializer_class(self): def get_serializer_class(self):
if self.action == "list": if self.action == "list":
return self.list_serializer_class return self.list_serializer_class
@ -166,29 +165,6 @@ class ProjectViewSet(HistoryResourceMixin, ModelCrudViewSet):
self.check_permissions(request, "tags_colors", project) self.check_permissions(request, "tags_colors", project)
return response.Ok(dict(project.tags_colors)) return response.Ok(dict(project.tags_colors))
@detail_route(methods=["POST"])
def star(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, "star", project)
votes_service.add_vote(project, user=request.user)
return response.Ok()
@detail_route(methods=["POST"])
def unstar(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, "unstar", project)
votes_service.remove_vote(project, user=request.user)
return response.Ok()
@detail_route(methods=["GET"])
def fans(self, request, pk=None):
project = self.get_object()
self.check_permissions(request, "fans", project)
voters = votes_service.get_voters(project)
voters_data = votes_serializers.VoterSerializer(voters, many=True)
return response.Ok(voters_data.data)
@detail_route(methods=["POST"]) @detail_route(methods=["POST"])
def create_template(self, request, **kwargs): def create_template(self, request, **kwargs):
template_name = request.DATA.get('template_name', None) template_name = request.DATA.get('template_name', None)
@ -287,6 +263,10 @@ class ProjectViewSet(HistoryResourceMixin, ModelCrudViewSet):
return response.NoContent() return response.NoContent()
class ProjectFansViewSet(VotersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.ProjectFansPermission,)
resource_model = models.Project
###################################################### ######################################################
## Custom values for selectors ## Custom values for selectors

View File

@ -16,7 +16,7 @@
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.db.models import Q from django.db.models import Q
from django.http import Http404, HttpResponse from django.http import HttpResponse
from taiga.base import filters from taiga.base import filters
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
@ -33,16 +33,17 @@ from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.models import Project, IssueStatus, Severity, Priority, IssueType from taiga.projects.models import Project, IssueStatus, Severity, Priority, IssueType
from taiga.projects.milestones.models import Milestone from taiga.projects.milestones.models import Milestone
from taiga.projects.votes.utils import attach_votescount_to_queryset from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
from taiga.projects.votes import services as votes_service
from taiga.projects.votes import serializers as votes_serializers
from . import models from . import models
from . import services from . import services
from . import permissions from . import permissions
from . import serializers from . import serializers
class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet): class IssueViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
ModelCrudViewSet):
queryset = models.Issue.objects.all()
permission_classes = (permissions.IssuePermission, ) permission_classes = (permissions.IssuePermission, )
filter_backends = (filters.CanViewIssuesFilterBackend, filter_backends = (filters.CanViewIssuesFilterBackend,
filters.OwnersFilter, filters.OwnersFilter,
@ -139,10 +140,9 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
return super().update(request, *args, **kwargs) return super().update(request, *args, **kwargs)
def get_queryset(self): def get_queryset(self):
qs = models.Issue.objects.all() qs = super().get_queryset()
qs = qs.prefetch_related("attachments") qs = qs.prefetch_related("attachments")
qs = attach_votescount_to_queryset(qs, as_field="votes_count") return self.attach_votes_attrs_to_queryset(qs)
return qs
def pre_save(self, obj): def pre_save(self, obj):
if not obj.id: if not obj.id:
@ -237,51 +237,7 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
return response.BadRequest(serializer.errors) return response.BadRequest(serializer.errors)
@detail_route(methods=['post'])
def upvote(self, request, pk=None):
issue = get_object_or_404(models.Issue, pk=pk)
self.check_permissions(request, 'upvote', issue) class IssueVotersViewSet(VotersViewSetMixin, ModelListViewSet):
votes_service.add_vote(issue, user=request.user)
return response.Ok()
@detail_route(methods=['post'])
def downvote(self, request, pk=None):
issue = get_object_or_404(models.Issue, pk=pk)
self.check_permissions(request, 'downvote', issue)
votes_service.remove_vote(issue, user=request.user)
return response.Ok()
class VotersViewSet(ModelListViewSet):
serializer_class = votes_serializers.VoterSerializer
list_serializer_class = votes_serializers.VoterSerializer
permission_classes = (permissions.IssueVotersPermission,) permission_classes = (permissions.IssueVotersPermission,)
resource_model = models.Issue
def retrieve(self, request, *args, **kwargs):
pk = kwargs.get("pk", None)
issue_id = kwargs.get("issue_id", None)
issue = get_object_or_404(models.Issue, pk=issue_id)
self.check_permissions(request, 'retrieve', issue)
try:
self.object = votes_service.get_voters(issue).get(pk=pk)
except User.DoesNotExist:
raise Http404
serializer = self.get_serializer(self.object)
return response.Ok(serializer.data)
def list(self, request, *args, **kwargs):
issue_id = kwargs.get("issue_id", None)
issue = get_object_or_404(models.Issue, pk=issue_id)
self.check_permissions(request, 'list', issue)
return super().list(request, *args, **kwargs)
def get_queryset(self):
issue = models.Issue.objects.get(pk=self.kwargs.get("issue_id"))
return votes_service.get_voters(issue)

View File

@ -31,10 +31,10 @@ class IssuePermission(TaigaResourcePermission):
list_perms = AllowAny() list_perms = AllowAny()
filters_data_perms = AllowAny() filters_data_perms = AllowAny()
csv_perms = AllowAny() csv_perms = AllowAny()
upvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
downvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
bulk_create_perms = HasProjectPerm('add_issue') bulk_create_perms = HasProjectPerm('add_issue')
delete_comment_perms= HasProjectPerm('modify_issue') delete_comment_perms= HasProjectPerm('modify_issue')
upvote_perms = IsAuthenticated() & HasProjectPerm('view_issues')
downvote_perms = IsAuthenticated() & HasProjectPerm('view_issues')
class HasIssueIdUrlParam(PermissionComponent): class HasIssueIdUrlParam(PermissionComponent):
@ -49,8 +49,4 @@ class IssueVotersPermission(TaigaResourcePermission):
enought_perms = IsProjectOwner() | IsSuperUser() enought_perms = IsProjectOwner() | IsSuperUser()
global_perms = None global_perms = None
retrieve_perms = HasProjectPerm('view_issues') retrieve_perms = HasProjectPerm('view_issues')
create_perms = HasProjectPerm('add_issue')
update_perms = HasProjectPerm('modify_issue')
partial_update_perms = HasProjectPerm('modify_issue')
destroy_perms = HasProjectPerm('delete_issue')
list_perms = HasProjectPerm('view_issues') list_perms = HasProjectPerm('view_issues')

View File

@ -19,17 +19,18 @@ from taiga.base.fields import TagsField
from taiga.base.fields import PgArrayField from taiga.base.fields import PgArrayField
from taiga.base.neighbors import NeighborsSerializerMixin from taiga.base.neighbors import NeighborsSerializerMixin
from taiga.mdrender.service import render as mdrender from taiga.mdrender.service import render as mdrender
from taiga.projects.validators import ProjectExistsValidator from taiga.projects.validators import ProjectExistsValidator
from taiga.projects.notifications.validators import WatchersValidator from taiga.projects.notifications.validators import WatchersValidator
from taiga.projects.serializers import BasicIssueStatusSerializer from taiga.projects.serializers import BasicIssueStatusSerializer
from taiga.projects.votes.mixins.serializers import VotedResourceSerializerMixin
from taiga.users.serializers import UserBasicInfoSerializer from taiga.users.serializers import UserBasicInfoSerializer
from . import models from . import models
class IssueSerializer(WatchersValidator, serializers.ModelSerializer): class IssueSerializer(WatchersValidator, VotedResourceSerializerMixin, serializers.ModelSerializer):
tags = TagsField(required=False) tags = TagsField(required=False)
external_reference = PgArrayField(required=False) external_reference = PgArrayField(required=False)
is_closed = serializers.Field(source="is_closed") is_closed = serializers.Field(source="is_closed")
@ -37,7 +38,6 @@ class IssueSerializer(WatchersValidator, serializers.ModelSerializer):
generated_user_stories = serializers.SerializerMethodField("get_generated_user_stories") generated_user_stories = serializers.SerializerMethodField("get_generated_user_stories")
blocked_note_html = serializers.SerializerMethodField("get_blocked_note_html") blocked_note_html = serializers.SerializerMethodField("get_blocked_note_html")
description_html = serializers.SerializerMethodField("get_description_html") description_html = serializers.SerializerMethodField("get_description_html")
votes = serializers.SerializerMethodField("get_votes_number")
status_extra_info = BasicIssueStatusSerializer(source="status", required=False, read_only=True) status_extra_info = BasicIssueStatusSerializer(source="status", required=False, read_only=True)
assigned_to_extra_info = UserBasicInfoSerializer(source="assigned_to", required=False, read_only=True) assigned_to_extra_info = UserBasicInfoSerializer(source="assigned_to", required=False, read_only=True)
owner_extra_info = UserBasicInfoSerializer(source="owner", required=False, read_only=True) owner_extra_info = UserBasicInfoSerializer(source="owner", required=False, read_only=True)
@ -59,10 +59,6 @@ class IssueSerializer(WatchersValidator, serializers.ModelSerializer):
def get_description_html(self, obj): def get_description_html(self, obj):
return mdrender(obj.project, obj.description) return mdrender(obj.project, obj.description)
def get_votes_number(self, obj):
# The "votes_count" attribute is attached in the get_queryset of the viewset.
return getattr(obj, "votes_count", 0)
class IssueListSerializer(IssueSerializer): class IssueListSerializer(IssueSerializer):
class Meta: class Meta:

View File

@ -54,19 +54,25 @@ class ProjectPermission(TaigaResourcePermission):
list_perms = AllowAny() list_perms = AllowAny()
stats_perms = HasProjectPerm('view_project') stats_perms = HasProjectPerm('view_project')
member_stats_perms = HasProjectPerm('view_project') member_stats_perms = HasProjectPerm('view_project')
issues_stats_perms = HasProjectPerm('view_project')
regenerate_userstories_csv_uuid_perms = IsProjectOwner() regenerate_userstories_csv_uuid_perms = IsProjectOwner()
regenerate_issues_csv_uuid_perms = IsProjectOwner() regenerate_issues_csv_uuid_perms = IsProjectOwner()
regenerate_tasks_csv_uuid_perms = IsProjectOwner() regenerate_tasks_csv_uuid_perms = IsProjectOwner()
star_perms = IsAuthenticated()
unstar_perms = IsAuthenticated()
issues_stats_perms = HasProjectPerm('view_project')
tags_perms = HasProjectPerm('view_project') tags_perms = HasProjectPerm('view_project')
tags_colors_perms = HasProjectPerm('view_project') tags_colors_perms = HasProjectPerm('view_project')
fans_perms = HasProjectPerm('view_project') star_perms = IsAuthenticated() & HasProjectPerm('view_project')
unstar_perms = IsAuthenticated() & HasProjectPerm('view_project')
create_template_perms = IsSuperUser() create_template_perms = IsSuperUser()
leave_perms = CanLeaveProject() leave_perms = CanLeaveProject()
class ProjectFansPermission(TaigaResourcePermission):
enought_perms = IsProjectOwner() | IsSuperUser()
global_perms = None
retrieve_perms = HasProjectPerm('view_project')
list_perms = HasProjectPerm('view_project')
class MembershipPermission(TaigaResourcePermission): class MembershipPermission(TaigaResourcePermission):
retrieve_perms = HasProjectPerm('view_project') retrieve_perms = HasProjectPerm('view_project')
create_perms = IsProjectOwner() create_perms = IsProjectOwner()

View File

@ -40,7 +40,7 @@ from .validators import ProjectExistsValidator
from .custom_attributes.serializers import UserStoryCustomAttributeSerializer from .custom_attributes.serializers import UserStoryCustomAttributeSerializer
from .custom_attributes.serializers import TaskCustomAttributeSerializer from .custom_attributes.serializers import TaskCustomAttributeSerializer
from .custom_attributes.serializers import IssueCustomAttributeSerializer from .custom_attributes.serializers import IssueCustomAttributeSerializer
from .votes.mixins.serializers import StarredResourceSerializerMixin
###################################################### ######################################################
## Custom values for selectors ## Custom values for selectors
@ -305,11 +305,10 @@ class ProjectMemberSerializer(serializers.ModelSerializer):
## Projects ## Projects
###################################################### ######################################################
class ProjectSerializer(serializers.ModelSerializer): class ProjectSerializer(StarredResourceSerializerMixin, serializers.ModelSerializer):
tags = TagsField(default=[], required=False) tags = TagsField(default=[], required=False)
anon_permissions = PgArrayField(required=False) anon_permissions = PgArrayField(required=False)
public_permissions = PgArrayField(required=False) public_permissions = PgArrayField(required=False)
stars = serializers.SerializerMethodField("get_stars_number")
my_permissions = serializers.SerializerMethodField("get_my_permissions") my_permissions = serializers.SerializerMethodField("get_my_permissions")
i_am_owner = serializers.SerializerMethodField("get_i_am_owner") i_am_owner = serializers.SerializerMethodField("get_i_am_owner")
tags_colors = TagsColorsField(required=False) tags_colors = TagsColorsField(required=False)
@ -321,10 +320,6 @@ class ProjectSerializer(serializers.ModelSerializer):
exclude = ("last_us_ref", "last_task_ref", "last_issue_ref", exclude = ("last_us_ref", "last_task_ref", "last_issue_ref",
"issues_csv_uuid", "tasks_csv_uuid", "userstories_csv_uuid") "issues_csv_uuid", "tasks_csv_uuid", "userstories_csv_uuid")
def get_stars_number(self, obj):
# The "stars_count" attribute is attached in the get_queryset of the viewset.
return getattr(obj, "stars_count", 0)
def get_my_permissions(self, obj): def get_my_permissions(self, obj):
if "request" in self.context: if "request" in self.context:
return get_user_project_permissions(self.context["request"].user, obj) return get_user_project_permissions(self.context["request"].user, obj)

View File

@ -20,13 +20,14 @@ from taiga.base.api.utils import get_object_or_404
from taiga.base import filters, response from taiga.base import filters, response
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.base.decorators import list_route from taiga.base.decorators import list_route
from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.projects.models import Project, TaskStatus from taiga.projects.models import Project, TaskStatus
from django.http import HttpResponse from django.http import HttpResponse
from taiga.projects.notifications.mixins import WatchedResourceMixin from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.history.mixins import HistoryResourceMixin from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.occ import OCCResourceMixin from taiga.projects.occ import OCCResourceMixin
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
from . import models from . import models
@ -35,8 +36,9 @@ from . import serializers
from . import services from . import services
class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet): class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
model = models.Task ModelCrudViewSet):
queryset = models.Task.objects.all()
permission_classes = (permissions.TaskPermission,) permission_classes = (permissions.TaskPermission,)
filter_backends = (filters.CanViewTasksFilterBackend,) filter_backends = (filters.CanViewTasksFilterBackend,)
filter_fields = ["user_story", "milestone", "project", "assigned_to", filter_fields = ["user_story", "milestone", "project", "assigned_to",
@ -82,6 +84,9 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
return super().update(request, *args, **kwargs) return super().update(request, *args, **kwargs)
def get_queryset(self):
qs = super().get_queryset()
return self.attach_votes_attrs_to_queryset(qs)
def pre_save(self, obj): def pre_save(self, obj):
if obj.user_story: if obj.user_story:
@ -165,3 +170,8 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
@list_route(methods=["POST"]) @list_route(methods=["POST"])
def bulk_update_us_order(self, request, **kwargs): def bulk_update_us_order(self, request, **kwargs):
return self._bulk_update_order("us_order", request, **kwargs) return self._bulk_update_order("us_order", request, **kwargs)
class TaskVotersViewSet(VotersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.TaskVotersPermission,)
resource_model = models.Task

View File

@ -15,7 +15,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api.permissions import (TaigaResourcePermission, HasProjectPerm, from taiga.base.api.permissions import (TaigaResourcePermission, HasProjectPerm,
IsProjectOwner, AllowAny, IsSuperUser) IsAuthenticated, IsProjectOwner, AllowAny,
IsSuperUser)
class TaskPermission(TaigaResourcePermission): class TaskPermission(TaigaResourcePermission):
@ -30,3 +31,12 @@ class TaskPermission(TaigaResourcePermission):
csv_perms = AllowAny() csv_perms = AllowAny()
bulk_create_perms = HasProjectPerm('add_task') bulk_create_perms = HasProjectPerm('add_task')
bulk_update_order_perms = HasProjectPerm('modify_task') bulk_update_order_perms = HasProjectPerm('modify_task')
upvote_perms = IsAuthenticated() & HasProjectPerm('view_tasks')
downvote_perms = IsAuthenticated() & HasProjectPerm('view_tasks')
class TaskVotersPermission(TaigaResourcePermission):
enought_perms = IsProjectOwner() | IsSuperUser()
global_perms = None
retrieve_perms = HasProjectPerm('view_tasks')
list_perms = HasProjectPerm('view_tasks')

View File

@ -27,12 +27,14 @@ from taiga.projects.milestones.validators import SprintExistsValidator
from taiga.projects.tasks.validators import TaskExistsValidator from taiga.projects.tasks.validators import TaskExistsValidator
from taiga.projects.notifications.validators import WatchersValidator from taiga.projects.notifications.validators import WatchersValidator
from taiga.projects.serializers import BasicTaskStatusSerializerSerializer from taiga.projects.serializers import BasicTaskStatusSerializerSerializer
from taiga.projects.votes.mixins.serializers import VotedResourceSerializerMixin
from taiga.users.serializers import UserBasicInfoSerializer from taiga.users.serializers import UserBasicInfoSerializer
from . import models from . import models
class TaskSerializer(WatchersValidator, serializers.ModelSerializer): class TaskSerializer(WatchersValidator, VotedResourceSerializerMixin, serializers.ModelSerializer):
tags = TagsField(required=False, default=[]) tags = TagsField(required=False, default=[])
external_reference = PgArrayField(required=False) external_reference = PgArrayField(required=False)
comment = serializers.SerializerMethodField("get_comment") comment = serializers.SerializerMethodField("get_comment")

View File

@ -28,15 +28,15 @@ from taiga.base import exceptions as exc
from taiga.base import response from taiga.base import response
from taiga.base import status from taiga.base import status
from taiga.base.decorators import list_route from taiga.base.decorators import list_route
from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base.api.utils import get_object_or_404 from taiga.base.api.utils import get_object_or_404
from taiga.projects.notifications.mixins import WatchedResourceMixin from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.history.mixins import HistoryResourceMixin from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.occ import OCCResourceMixin from taiga.projects.occ import OCCResourceMixin
from taiga.projects.models import Project, UserStoryStatus from taiga.projects.models import Project, UserStoryStatus
from taiga.projects.history.services import take_snapshot from taiga.projects.history.services import take_snapshot
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
from . import models from . import models
from . import permissions from . import permissions
@ -44,8 +44,9 @@ from . import serializers
from . import services from . import services
class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet): class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
model = models.UserStory ModelCrudViewSet):
queryset = models.UserStory.objects.all()
permission_classes = (permissions.UserStoryPermission,) permission_classes = (permissions.UserStoryPermission,)
filter_backends = (filters.CanViewUsFilterBackend, filter_backends = (filters.CanViewUsFilterBackend,
filters.OwnersFilter, filters.OwnersFilter,
@ -109,13 +110,13 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.all() qs = super().get_queryset()
qs = qs.prefetch_related("role_points", qs = qs.prefetch_related("role_points",
"role_points__points", "role_points__points",
"role_points__role", "role_points__role",
"watchers") "watchers")
qs = qs.select_related("milestone", "project") qs = qs.select_related("milestone", "project")
return qs return self.attach_votes_attrs_to_queryset(qs)
def pre_save(self, obj): def pre_save(self, obj):
# This is very ugly hack, but having # This is very ugly hack, but having
@ -264,3 +265,7 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
self.send_notifications(self.object.generated_from_issue, history) self.send_notifications(self.object.generated_from_issue, history)
return response return response
class UserStoryVotersViewSet(VotersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.UserStoryVotersPermission,)
resource_model = models.UserStory

View File

@ -30,3 +30,12 @@ class UserStoryPermission(TaigaResourcePermission):
csv_perms = AllowAny() csv_perms = AllowAny()
bulk_create_perms = IsAuthenticated() & (HasProjectPerm('add_us_to_project') | HasProjectPerm('add_us')) bulk_create_perms = IsAuthenticated() & (HasProjectPerm('add_us_to_project') | HasProjectPerm('add_us'))
bulk_update_order_perms = HasProjectPerm('modify_us') bulk_update_order_perms = HasProjectPerm('modify_us')
upvote_perms = IsAuthenticated() & HasProjectPerm('view_us')
downvote_perms = IsAuthenticated() & HasProjectPerm('view_us')
class UserStoryVotersPermission(TaigaResourcePermission):
enought_perms = IsProjectOwner() | IsSuperUser()
global_perms = None
retrieve_perms = HasProjectPerm('view_us')
list_perms = HasProjectPerm('view_us')

View File

@ -27,6 +27,8 @@ from taiga.projects.validators import UserStoryStatusExistsValidator
from taiga.projects.userstories.validators import UserStoryExistsValidator from taiga.projects.userstories.validators import UserStoryExistsValidator
from taiga.projects.notifications.validators import WatchersValidator from taiga.projects.notifications.validators import WatchersValidator
from taiga.projects.serializers import BasicUserStoryStatusSerializer from taiga.projects.serializers import BasicUserStoryStatusSerializer
from taiga.projects.votes.mixins.serializers import VotedResourceSerializerMixin
from taiga.users.serializers import UserBasicInfoSerializer from taiga.users.serializers import UserBasicInfoSerializer
from . import models from . import models
@ -42,7 +44,7 @@ class RolePointsField(serializers.WritableField):
return json.loads(obj) return json.loads(obj)
class UserStorySerializer(WatchersValidator, serializers.ModelSerializer): class UserStorySerializer(WatchersValidator, VotedResourceSerializerMixin, serializers.ModelSerializer):
tags = TagsField(default=[], required=False) tags = TagsField(default=[], required=False)
external_reference = PgArrayField(required=False) external_reference = PgArrayField(required=False)
points = RolePointsField(source="role_points", required=False) points = RolePointsField(source="role_points", required=False)

View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.utils.timezone import utc
from django.conf import settings
import datetime
class Migration(migrations.Migration):
dependencies = [
('votes', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='vote',
name='created_date',
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2015, 8, 5, 16, 0, 40, 158374, tzinfo=utc), verbose_name='created date'),
preserve_default=False,
),
migrations.AlterField(
model_name='vote',
name='user',
field=models.ForeignKey(related_name='votes', to=settings.AUTH_USER_MODEL, verbose_name='user'),
preserve_default=True,
),
migrations.AlterField(
model_name='votes',
name='count',
field=models.PositiveIntegerField(default=0, verbose_name='count'),
preserve_default=True,
),
]

View File

View File

@ -0,0 +1,37 @@
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api import serializers
class BaseVotedResourceSerializer(serializers.ModelSerializer):
def get_votes_counter(self, obj):
# The "votes_count" attribute is attached in the get_queryset of the viewset.
return getattr(obj, "votes_count", 0) or 0
def get_is_voted(self, obj):
# The "is_voted" attribute is attached in the get_queryset of the viewset.
return getattr(obj, "is_voted", False) or False
class StarredResourceSerializerMixin(BaseVotedResourceSerializer):
stars = serializers.SerializerMethodField("get_votes_counter")
is_starred = serializers.SerializerMethodField("get_is_voted")
class VotedResourceSerializerMixin(BaseVotedResourceSerializer):
votes = serializers.SerializerMethodField("get_votes_counter")
is_voted = serializers.SerializerMethodField("get_is_voted")

View File

@ -0,0 +1,114 @@
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.core.exceptions import ObjectDoesNotExist
from taiga.base import response
from taiga.base.api import viewsets
from taiga.base.api.utils import get_object_or_404
from taiga.base.decorators import detail_route
from taiga.projects.votes import serializers
from taiga.projects.votes import services
from taiga.projects.votes.utils import attach_votes_count_to_queryset, attach_is_vote_to_queryset
class BaseVotedResource:
# Note: Update get_queryset method:
# def get_queryset(self):
# qs = super().get_queryset()
# return self.attach_votes_attrs_to_queryset(qs)
def attach_votes_attrs_to_queryset(self, queryset):
qs = attach_votes_count_to_queryset(queryset)
if self.request.user.is_authenticated():
qs = attach_is_vote_to_queryset(self.request.user, qs)
return qs
def _add_voter(self, permission, request, pk=None):
obj = self.get_object()
self.check_permissions(request, permission, obj)
services.add_vote(obj, user=request.user)
return response.Ok()
def _remove_vote(self, permission, request, pk=None):
obj = self.get_object()
self.check_permissions(request, permission, obj)
services.remove_vote(obj, user=request.user)
return response.Ok()
class StarredResourceMixin(BaseVotedResource):
# Note: objects nedd 'star' and 'unstar' permissions.
@detail_route(methods=["POST"])
def star(self, request, pk=None):
return self._add_voter("star", request, pk)
@detail_route(methods=["POST"])
def unstar(self, request, pk=None):
return self._remove_vote("unstar", request, pk)
class VotedResourceMixin(BaseVotedResource):
# Note: objects nedd 'upvote' and 'downvote' permissions.
@detail_route(methods=["POST"])
def upvote(self, request, pk=None):
return self._add_voter("upvote", request, pk)
@detail_route(methods=["POST"])
def downvote(self, request, pk=None):
return self._remove_vote("downvote", request, pk)
class VotersViewSetMixin:
# Is a ModelListViewSet with two required params: permission_classes and resource_model
serializer_class = serializers.VoterSerializer
list_serializer_class = serializers.VoterSerializer
permission_classes = None
resource_model = None
def retrieve(self, request, *args, **kwargs):
pk = kwargs.get("pk", None)
resource_id = kwargs.get("resource_id", None)
resource = get_object_or_404(self.resource_model, pk=resource_id)
self.check_permissions(request, 'retrieve', resource)
try:
self.object = services.get_voters(resource).get(pk=pk)
except ObjectDoesNotExist: # or User.DoesNotExist
return response.NotFound()
serializer = self.get_serializer(self.object)
return response.Ok(serializer.data)
def list(self, request, *args, **kwargs):
resource_id = kwargs.get("resource_id", None)
resource = get_object_or_404(self.resource_model, pk=resource_id)
self.check_permissions(request, 'list', resource)
return super().list(request, *args, **kwargs)
def get_queryset(self):
resource = self.resource_model.objects.get(pk=self.kwargs.get("resource_id"))
return services.get_voters(resource)

View File

@ -16,16 +16,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes import generic
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes import generic
class Votes(models.Model): class Votes(models.Model):
content_type = models.ForeignKey("contenttypes.ContentType") content_type = models.ForeignKey("contenttypes.ContentType")
object_id = models.PositiveIntegerField() object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey("content_type", "object_id") content_object = generic.GenericForeignKey("content_type", "object_id")
count = models.PositiveIntegerField(default=0) count = models.PositiveIntegerField(null=False, blank=False, default=0, verbose_name=_("count"))
class Meta: class Meta:
verbose_name = _("Votes") verbose_name = _("Votes")
@ -44,10 +44,12 @@ class Votes(models.Model):
class Vote(models.Model): class Vote(models.Model):
content_type = models.ForeignKey("contenttypes.ContentType") content_type = models.ForeignKey("contenttypes.ContentType")
object_id = models.PositiveIntegerField(null=False) object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey("content_type", "object_id") content_object = generic.GenericForeignKey("content_type", "object_id")
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False, user = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
related_name="votes", verbose_name=_("votes")) related_name="votes", verbose_name=_("user"))
created_date = models.DateTimeField(null=False, blank=False, auto_now_add=True,
verbose_name=_("created date"))
class Meta: class Meta:
verbose_name = _("Vote") verbose_name = _("Vote")

View File

@ -18,7 +18,7 @@
from django.apps import apps from django.apps import apps
def attach_votescount_to_queryset(queryset, as_field="votes_count"): def attach_votes_count_to_queryset(queryset, as_field="votes_count"):
"""Attach votes count to each object of the queryset. """Attach votes count to each object of the queryset.
Because of laziness of vote objects creation, this makes much simpler and more efficient to Because of laziness of vote objects creation, this makes much simpler and more efficient to
@ -34,8 +34,40 @@ def attach_votescount_to_queryset(queryset, as_field="votes_count"):
""" """
model = queryset.model model = queryset.model
type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(model) type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(model)
sql = ("SELECT coalesce(votes_votes.count, 0) FROM votes_votes " sql = ("""SELECT coalesce(votes_votes.count, 0)
"WHERE votes_votes.content_type_id = {type_id} AND votes_votes.object_id = {tbl}.id") FROM votes_votes
WHERE votes_votes.content_type_id = {type_id}
AND votes_votes.object_id = {tbl}.id""")
sql = sql.format(type_id=type.id, tbl=model._meta.db_table) sql = sql.format(type_id=type.id, tbl=model._meta.db_table)
qs = queryset.extra(select={as_field: sql}) qs = queryset.extra(select={as_field: sql})
return qs return qs
def attach_is_vote_to_queryset(user, queryset, as_field="is_voted"):
"""Attach is_vote boolean to each object of the queryset.
Because of laziness of vote objects creation, this makes much simpler and more efficient to
access to votes-object and check if the curren user vote it.
(The other way was to do it in the serializer with some try/except blocks and additional
queries)
:param user: A users.User object model
:param queryset: A Django queryset object.
:param as_field: Attach the boolean as an attribute with this name.
:return: Queryset object with the additional `as_field` field.
"""
model = queryset.model
type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(model)
sql = ("""SELECT CASE WHEN (SELECT count(*)
FROM votes_vote
WHERE votes_vote.content_type_id = {type_id}
AND votes_vote.object_id = {tbl}.id
AND votes_vote.user_id = {user_id}) > 0
THEN TRUE
ELSE FALSE
END""")
sql = sql.format(type_id=type.id, tbl=model._meta.db_table, user_id=user.id)
qs = queryset.extra(select={as_field: sql})
return qs

View File

@ -1,4 +1,3 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be> # Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com> # Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com> # Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
@ -49,6 +48,7 @@ router.register(r"notify-policies", NotifyPolicyViewSet, base_name="notification
# Projects & Selectors # Projects & Selectors
from taiga.projects.api import ProjectViewSet from taiga.projects.api import ProjectViewSet
from taiga.projects.api import ProjectFansViewSet
from taiga.projects.api import MembershipViewSet from taiga.projects.api import MembershipViewSet
from taiga.projects.api import InvitationViewSet from taiga.projects.api import InvitationViewSet
from taiga.projects.api import UserStoryStatusViewSet from taiga.projects.api import UserStoryStatusViewSet
@ -61,6 +61,7 @@ from taiga.projects.api import SeverityViewSet
from taiga.projects.api import ProjectTemplateViewSet from taiga.projects.api import ProjectTemplateViewSet
router.register(r"projects", ProjectViewSet, base_name="projects") router.register(r"projects", ProjectViewSet, base_name="projects")
router.register(r"projects/(?P<resource_id>\d+)/fans", ProjectFansViewSet, base_name="project-fans")
router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates") router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates")
router.register(r"memberships", MembershipViewSet, base_name="memberships") router.register(r"memberships", MembershipViewSet, base_name="memberships")
router.register(r"invitations", InvitationViewSet, base_name="invitations") router.register(r"invitations", InvitationViewSet, base_name="invitations")
@ -124,20 +125,26 @@ router.register(r"wiki/attachments", WikiAttachmentViewSet, base_name="wiki-atta
# Project components # Project components
from taiga.projects.milestones.api import MilestoneViewSet from taiga.projects.milestones.api import MilestoneViewSet
from taiga.projects.userstories.api import UserStoryViewSet from taiga.projects.userstories.api import UserStoryViewSet
from taiga.projects.userstories.api import UserStoryVotersViewSet
from taiga.projects.tasks.api import TaskViewSet from taiga.projects.tasks.api import TaskViewSet
from taiga.projects.tasks.api import TaskVotersViewSet
from taiga.projects.issues.api import IssueViewSet from taiga.projects.issues.api import IssueViewSet
from taiga.projects.issues.api import VotersViewSet from taiga.projects.issues.api import IssueVotersViewSet
from taiga.projects.wiki.api import WikiViewSet, WikiLinkViewSet from taiga.projects.wiki.api import WikiViewSet, WikiLinkViewSet
router.register(r"milestones", MilestoneViewSet, base_name="milestones") router.register(r"milestones", MilestoneViewSet, base_name="milestones")
router.register(r"userstories", UserStoryViewSet, base_name="userstories") router.register(r"userstories", UserStoryViewSet, base_name="userstories")
router.register(r"userstories/(?P<resource_id>\d+)/voters", UserStoryVotersViewSet, base_name="userstory-voters")
router.register(r"tasks", TaskViewSet, base_name="tasks") router.register(r"tasks", TaskViewSet, base_name="tasks")
router.register(r"tasks/(?P<resource_id>\d+)/voters", TaskVotersViewSet, base_name="task-voters")
router.register(r"issues", IssueViewSet, base_name="issues") router.register(r"issues", IssueViewSet, base_name="issues")
router.register(r"issues/(?P<issue_id>\d+)/voters", VotersViewSet, base_name="issue-voters") router.register(r"issues/(?P<resource_id>\d+)/voters", IssueVotersViewSet, base_name="issue-voters")
router.register(r"wiki", WikiViewSet, base_name="wiki") router.register(r"wiki", WikiViewSet, base_name="wiki")
router.register(r"wiki-links", WikiLinkViewSet, base_name="wiki-links") router.register(r"wiki-links", WikiLinkViewSet, base_name="wiki-links")
# History & Components # History & Components
from taiga.projects.history.api import UserStoryHistory from taiga.projects.history.api import UserStoryHistory
from taiga.projects.history.api import TaskHistory from taiga.projects.history.api import TaskHistory

View File

@ -224,15 +224,6 @@ class UsersViewSet(ModelCrudViewSet):
user_data = self.admin_serializer_class(request.user).data user_data = self.admin_serializer_class(request.user).data
return response.Ok(user_data) return response.Ok(user_data)
@detail_route(methods=["GET"])
def starred(self, request, pk=None):
user = self.get_object()
self.check_permissions(request, 'starred', user)
stars = votes_service.get_voted(user.pk, model=apps.get_model('projects', 'Project'))
stars_data = StarredSerializer(stars, many=True)
return response.Ok(stars_data.data)
#TODO: commit_on_success #TODO: commit_on_success
def partial_update(self, request, *args, **kwargs): def partial_update(self, request, *args, **kwargs):
""" """

View File

@ -477,12 +477,10 @@ def test_issue_action_upvote(client, data):
results = helper_test_http_method(client, 'post', public_url, "", users) results = helper_test_http_method(client, 'post', public_url, "", users)
assert results == [401, 200, 200, 200, 200] assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url1, "", users) results = helper_test_http_method(client, 'post', private_url1, "", users)
assert results == [401, 200, 200, 200, 200] assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users) results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [401, 403, 403, 200, 200] assert results == [404, 404, 404, 200, 200]
def test_issue_action_downvote(client, data): def test_issue_action_downvote(client, data):
@ -500,18 +498,16 @@ def test_issue_action_downvote(client, data):
results = helper_test_http_method(client, 'post', public_url, "", users) results = helper_test_http_method(client, 'post', public_url, "", users)
assert results == [401, 200, 200, 200, 200] assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url1, "", users) results = helper_test_http_method(client, 'post', private_url1, "", users)
assert results == [401, 200, 200, 200, 200] assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users) results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [401, 403, 403, 200, 200] assert results == [404, 404, 404, 200, 200]
def test_issue_voters_list(client, data): def test_issue_voters_list(client, data):
public_url = reverse('issue-voters-list', kwargs={"issue_id": data.public_issue.pk}) public_url = reverse('issue-voters-list', kwargs={"resource_id": data.public_issue.pk})
private_url1 = reverse('issue-voters-list', kwargs={"issue_id": data.private_issue1.pk}) private_url1 = reverse('issue-voters-list', kwargs={"resource_id": data.private_issue1.pk})
private_url2 = reverse('issue-voters-list', kwargs={"issue_id": data.private_issue2.pk}) private_url2 = reverse('issue-voters-list', kwargs={"resource_id": data.private_issue2.pk})
users = [ users = [
None, None,
@ -523,21 +519,22 @@ def test_issue_voters_list(client, data):
results = helper_test_http_method(client, 'get', public_url, None, users) results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200] assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users) results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200] assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users) results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200] assert results == [401, 403, 403, 200, 200]
def test_issue_voters_retrieve(client, data): def test_issue_voters_retrieve(client, data):
add_vote(data.public_issue, data.project_owner) add_vote(data.public_issue, data.project_owner)
public_url = reverse('issue-voters-detail', kwargs={"issue_id": data.public_issue.pk, "pk": data.project_owner.pk}) public_url = reverse('issue-voters-detail', kwargs={"resource_id": data.public_issue.pk,
"pk": data.project_owner.pk})
add_vote(data.private_issue1, data.project_owner) add_vote(data.private_issue1, data.project_owner)
private_url1 = reverse('issue-voters-detail', kwargs={"issue_id": data.private_issue1.pk, "pk": data.project_owner.pk}) private_url1 = reverse('issue-voters-detail', kwargs={"resource_id": data.private_issue1.pk,
"pk": data.project_owner.pk})
add_vote(data.private_issue2, data.project_owner) add_vote(data.private_issue2, data.project_owner)
private_url2 = reverse('issue-voters-detail', kwargs={"issue_id": data.private_issue2.pk, "pk": data.project_owner.pk}) private_url2 = reverse('issue-voters-detail', kwargs={"resource_id": data.private_issue2.pk,
"pk": data.project_owner.pk})
users = [ users = [
None, None,
@ -549,10 +546,8 @@ def test_issue_voters_retrieve(client, data):
results = helper_test_http_method(client, 'get', public_url, None, users) results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200] assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users) results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200] assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users) results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200] assert results == [401, 403, 403, 200, 200]

View File

@ -198,6 +198,25 @@ def test_project_action_stats(client, data):
assert results == [404, 404, 200, 200] assert results == [404, 404, 200, 200]
def test_project_action_issues_stats(client, data):
public_url = reverse('projects-issues-stats', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-issues-stats', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-issues-stats', kwargs={"pk": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private1_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private2_url, None, users)
assert results == [404, 404, 200, 200]
def test_project_action_star(client, data): def test_project_action_star(client, data):
public_url = reverse('projects-star', kwargs={"pk": data.public_project.pk}) public_url = reverse('projects-star', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-star', kwargs={"pk": data.private_project1.pk}) private1_url = reverse('projects-star', kwargs={"pk": data.private_project1.pk})
@ -236,29 +255,10 @@ def test_project_action_unstar(client, data):
assert results == [404, 404, 200, 200] assert results == [404, 404, 200, 200]
def test_project_action_issues_stats(client, data): def test_project_fans_list(client, data):
public_url = reverse('projects-issues-stats', kwargs={"pk": data.public_project.pk}) public_url = reverse('project-fans-list', kwargs={"resource_id": data.public_project.pk})
private1_url = reverse('projects-issues-stats', kwargs={"pk": data.private_project1.pk}) private1_url = reverse('project-fans-list', kwargs={"resource_id": data.private_project1.pk})
private2_url = reverse('projects-issues-stats', kwargs={"pk": data.private_project2.pk}) private2_url = reverse('project-fans-list', kwargs={"resource_id": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private1_url, None, users)
assert results == [200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private2_url, None, users)
assert results == [404, 404, 200, 200]
def test_project_action_fans(client, data):
public_url = reverse('projects-fans', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-fans', kwargs={"pk": data.private_project1.pk})
private2_url = reverse('projects-fans', kwargs={"pk": data.private_project2.pk})
users = [ users = [
None, None,
@ -273,13 +273,16 @@ def test_project_action_fans(client, data):
results = helper_test_http_method_and_count(client, 'get', private1_url, None, users) results = helper_test_http_method_and_count(client, 'get', private1_url, None, users)
assert results == [(200, 2), (200, 2), (200, 2), (200, 2), (200, 2)] assert results == [(200, 2), (200, 2), (200, 2), (200, 2), (200, 2)]
results = helper_test_http_method_and_count(client, 'get', private2_url, None, users) results = helper_test_http_method_and_count(client, 'get', private2_url, None, users)
assert results == [(404, 1), (404, 1), (404, 1), (200, 2), (200, 2)] assert results == [(401, 0), (403, 0), (403, 0), (200, 2), (200, 2)]
def test_user_action_starred(client, data): def test_project_fans_retrieve(client, data):
url1 = reverse('users-starred', kwargs={"pk": data.project_member_without_perms.pk}) public_url = reverse('project-fans-detail', kwargs={"resource_id": data.public_project.pk,
url2 = reverse('users-starred', kwargs={"pk": data.project_member_with_perms.pk}) "pk": data.project_owner.pk})
url3 = reverse('users-starred', kwargs={"pk": data.project_owner.pk}) private1_url = reverse('project-fans-detail', kwargs={"resource_id": data.private_project1.pk,
"pk": data.project_owner.pk})
private2_url = reverse('project-fans-detail', kwargs={"resource_id": data.private_project2.pk,
"pk": data.project_owner.pk})
users = [ users = [
None, None,
@ -289,12 +292,12 @@ def test_user_action_starred(client, data):
data.project_owner data.project_owner
] ]
results = helper_test_http_method_and_count(client, 'get', url1, None, users) results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [(200, 0), (200, 0), (200, 0), (200, 0), (200, 0)] assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method_and_count(client, 'get', url2, None, users) results = helper_test_http_method(client, 'get', private1_url, None, users)
assert results == [(200, 3), (200, 3), (200, 3), (200, 3), (200, 3)] assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method_and_count(client, 'get', url3, None, users) results = helper_test_http_method(client, 'get', private2_url, None, users)
assert results == [(200, 3), (200, 3), (200, 3), (200, 3), (200, 3)] assert results == [401, 403, 403, 200, 200]
def test_project_action_create_template(client, data): def test_project_action_create_template(client, data):

View File

@ -9,6 +9,7 @@ from taiga.projects.occ import OCCResourceMixin
from tests import factories as f from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
from taiga.projects.votes.services import add_vote
from unittest import mock from unittest import mock
@ -416,6 +417,96 @@ def test_task_action_bulk_create(client, data):
assert results == [401, 403, 403, 200, 200] assert results == [401, 403, 403, 200, 200]
def test_task_action_upvote(client, data):
public_url = reverse('tasks-upvote', kwargs={"pk": data.public_task.pk})
private_url1 = reverse('tasks-upvote', kwargs={"pk": data.private_task1.pk})
private_url2 = reverse('tasks-upvote', kwargs={"pk": data.private_task2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url1, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [404, 404, 404, 200, 200]
def test_task_action_downvote(client, data):
public_url = reverse('tasks-downvote', kwargs={"pk": data.public_task.pk})
private_url1 = reverse('tasks-downvote', kwargs={"pk": data.private_task1.pk})
private_url2 = reverse('tasks-downvote', kwargs={"pk": data.private_task2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url1, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [404, 404, 404, 200, 200]
def test_task_voters_list(client, data):
public_url = reverse('task-voters-list', kwargs={"resource_id": data.public_task.pk})
private_url1 = reverse('task-voters-list', kwargs={"resource_id": data.private_task1.pk})
private_url2 = reverse('task-voters-list', kwargs={"resource_id": data.private_task2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_task_voters_retrieve(client, data):
add_vote(data.public_task, data.project_owner)
public_url = reverse('task-voters-detail', kwargs={"resource_id": data.public_task.pk,
"pk": data.project_owner.pk})
add_vote(data.private_task1, data.project_owner)
private_url1 = reverse('task-voters-detail', kwargs={"resource_id": data.private_task1.pk,
"pk": data.project_owner.pk})
add_vote(data.private_task2, data.project_owner)
private_url2 = reverse('task-voters-detail', kwargs={"resource_id": data.private_task2.pk,
"pk": data.project_owner.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_tasks_csv(client, data): def test_tasks_csv(client, data):
url = reverse('tasks-csv') url = reverse('tasks-csv')
csv_public_uuid = data.public_project.tasks_csv_uuid csv_public_uuid = data.public_project.tasks_csv_uuid

View File

@ -9,6 +9,7 @@ from taiga.projects.occ import OCCResourceMixin
from tests import factories as f from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
from taiga.projects.votes.services import add_vote
from unittest import mock from unittest import mock
@ -415,6 +416,95 @@ def test_user_story_action_bulk_update_order(client, data):
results = helper_test_http_method(client, 'post', url, post_data, users) results = helper_test_http_method(client, 'post', url, post_data, users)
assert results == [401, 403, 403, 204, 204] assert results == [401, 403, 403, 204, 204]
def test_user_story_action_upvote(client, data):
public_url = reverse('userstories-upvote', kwargs={"pk": data.public_user_story.pk})
private_url1 = reverse('userstories-upvote', kwargs={"pk": data.private_user_story1.pk})
private_url2 = reverse('userstories-upvote', kwargs={"pk": data.private_user_story2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url1, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [404, 404, 404, 200, 200]
def test_user_story_action_downvote(client, data):
public_url = reverse('userstories-downvote', kwargs={"pk": data.public_user_story.pk})
private_url1 = reverse('userstories-downvote', kwargs={"pk": data.private_user_story1.pk})
private_url2 = reverse('userstories-downvote', kwargs={"pk": data.private_user_story2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url1, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [404, 404, 404, 200, 200]
def test_user_story_voters_list(client, data):
public_url = reverse('userstory-voters-list', kwargs={"resource_id": data.public_user_story.pk})
private_url1 = reverse('userstory-voters-list', kwargs={"resource_id": data.private_user_story1.pk})
private_url2 = reverse('userstory-voters-list', kwargs={"resource_id": data.private_user_story2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_user_story_voters_retrieve(client, data):
add_vote(data.public_user_story, data.project_owner)
public_url = reverse('userstory-voters-detail', kwargs={"resource_id": data.public_user_story.pk,
"pk": data.project_owner.pk})
add_vote(data.private_user_story1, data.project_owner)
private_url1 = reverse('userstory-voters-detail', kwargs={"resource_id": data.private_user_story1.pk,
"pk": data.project_owner.pk})
add_vote(data.private_user_story2, data.project_owner)
private_url2 = reverse('userstory-voters-detail', kwargs={"resource_id": data.private_user_story2.pk,
"pk": data.project_owner.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_user_stories_csv(client, data): def test_user_stories_csv(client, data):
url = reverse('userstories-csv') url = reverse('userstories-csv')

View File

@ -0,0 +1,123 @@
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Anler Hernández <hello@anler.me>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from django.core.urlresolvers import reverse
from .. import factories as f
pytestmark = pytest.mark.django_db
def test_star_project(client):
user = f.UserFactory.create()
project = f.create_project(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
url = reverse("projects-star", args=(project.id,))
client.login(user)
response = client.post(url)
assert response.status_code == 200
def test_unstar_project(client):
user = f.UserFactory.create()
project = f.create_project(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
url = reverse("projects-unstar", args=(project.id,))
client.login(user)
response = client.post(url)
assert response.status_code == 200
def test_list_project_fans(client):
user = f.UserFactory.create()
project = f.create_project(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
f.VoteFactory.create(content_object=project, user=user)
url = reverse("project-fans-list", args=(project.id,))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data[0]['id'] == user.id
def test_get_project_fan(client):
user = f.UserFactory.create()
project = f.create_project(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
vote = f.VoteFactory.create(content_object=project, user=user)
url = reverse("project-fans-detail", args=(project.id, vote.user.id))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['id'] == vote.user.id
def test_get_project_stars(client):
user = f.UserFactory.create()
project = f.create_project(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
url = reverse("projects-detail", args=(project.id,))
f.VotesFactory.create(content_object=project, count=5)
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['stars'] == 5
def test_get_project_is_starred(client):
user = f.UserFactory.create()
project = f.create_project(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
f.VotesFactory.create(content_object=project)
url_detail = reverse("projects-detail", args=(project.id,))
url_star = reverse("projects-star", args=(project.id,))
url_unstar = reverse("projects-unstar", args=(project.id,))
client.login(user)
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['stars'] == 0
assert response.data['is_starred'] == False
response = client.post(url_star)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['stars'] == 1
assert response.data['is_starred'] == True
response = client.post(url_unstar)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['stars'] == 0
assert response.data['is_starred'] == False

View File

@ -1,115 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014 Anler Hernández <hello@anler.me>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from django.core.urlresolvers import reverse
from .. import factories as f
pytestmark = pytest.mark.django_db
def test_project_owner_star_project(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
f.MembershipFactory.create(project=project, is_owner=True, user=user)
url = reverse("projects-star", args=(project.id,))
client.login(user)
response = client.post(url)
assert response.status_code == 200
def test_project_owner_unstar_project(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
f.MembershipFactory.create(project=project, is_owner=True, user=user)
url = reverse("projects-unstar", args=(project.id,))
client.login(user)
response = client.post(url)
assert response.status_code == 200
def test_project_member_star_project(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create()
role = f.RoleFactory.create(project=project, permissions=["view_project"])
f.MembershipFactory.create(project=project, user=user, role=role)
url = reverse("projects-star", args=(project.id,))
client.login(user)
response = client.post(url)
assert response.status_code == 200
def test_project_member_unstar_project(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create()
role = f.RoleFactory.create(project=project, permissions=["view_project"])
f.MembershipFactory.create(project=project, user=user, role=role)
url = reverse("projects-unstar", args=(project.id,))
client.login(user)
response = client.post(url)
assert response.status_code == 200
def test_list_project_fans(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
fan = f.VoteFactory.create(content_object=project)
url = reverse("projects-fans", args=(project.id,))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data[0]['id'] == fan.user.id
def test_list_user_starred_projects(client):
user = f.UserFactory.create()
project = f.ProjectFactory()
url = reverse("users-starred", args=(user.id,))
f.VoteFactory.create(user=user, content_object=project)
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data[0]['id'] == project.id
def test_get_project_stars(client):
user = f.UserFactory.create()
project = f.ProjectFactory.create(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
url = reverse("projects-detail", args=(project.id,))
f.VotesFactory.create(content_object=project, count=5)
f.VotesFactory.create(count=3)
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['stars'] == 5

View File

@ -1,7 +1,7 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be> # Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com> # Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com> # Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2014 Anler Hernández <hello@anler.me> # Copyright (C) 2015 Anler Hernández <hello@anler.me>
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the # published by the Free Software Foundation, either version 3 of the
@ -51,8 +51,8 @@ def test_list_issue_voters(client):
user = f.UserFactory.create() user = f.UserFactory.create()
issue = f.create_issue(owner=user) issue = f.create_issue(owner=user)
f.MembershipFactory.create(project=issue.project, user=user, is_owner=True) f.MembershipFactory.create(project=issue.project, user=user, is_owner=True)
url = reverse("issue-voters-list", args=(issue.id,))
f.VoteFactory.create(content_object=issue, user=user) f.VoteFactory.create(content_object=issue, user=user)
url = reverse("issue-voters-list", args=(issue.id,))
client.login(user) client.login(user)
response = client.get(url) response = client.get(url)
@ -60,7 +60,6 @@ def test_list_issue_voters(client):
assert response.status_code == 200 assert response.status_code == 200
assert response.data[0]['id'] == user.id assert response.data[0]['id'] == user.id
def test_get_issue_voter(client): def test_get_issue_voter(client):
user = f.UserFactory.create() user = f.UserFactory.create()
issue = f.create_issue(owner=user) issue = f.create_issue(owner=user)
@ -74,7 +73,6 @@ def test_get_issue_voter(client):
assert response.status_code == 200 assert response.status_code == 200
assert response.data['id'] == vote.user.id assert response.data['id'] == vote.user.id
def test_get_issue_votes(client): def test_get_issue_votes(client):
user = f.UserFactory.create() user = f.UserFactory.create()
issue = f.create_issue(owner=user) issue = f.create_issue(owner=user)
@ -88,3 +86,36 @@ def test_get_issue_votes(client):
assert response.status_code == 200 assert response.status_code == 200
assert response.data['votes'] == 5 assert response.data['votes'] == 5
def test_get_issue_is_voted(client):
user = f.UserFactory.create()
issue = f.create_issue(owner=user)
f.MembershipFactory.create(project=issue.project, user=user, is_owner=True)
f.VotesFactory.create(content_object=issue)
url_detail = reverse("issues-detail", args=(issue.id,))
url_upvote = reverse("issues-upvote", args=(issue.id,))
url_downvote = reverse("issues-downvote", args=(issue.id,))
client.login(user)
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['votes'] == 0
assert response.data['is_voted'] == False
response = client.post(url_upvote)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['votes'] == 1
assert response.data['is_voted'] == True
response = client.post(url_downvote)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['votes'] == 0
assert response.data['is_voted'] == False

View File

@ -0,0 +1,123 @@
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Anler Hernández <hello@anler.me>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from django.core.urlresolvers import reverse
from .. import factories as f
pytestmark = pytest.mark.django_db
def test_upvote_task(client):
user = f.UserFactory.create()
task = f.create_task(owner=user)
f.MembershipFactory.create(project=task.project, user=user, is_owner=True)
url = reverse("tasks-upvote", args=(task.id,))
client.login(user)
response = client.post(url)
assert response.status_code == 200
def test_downvote_task(client):
user = f.UserFactory.create()
task = f.create_task(owner=user)
f.MembershipFactory.create(project=task.project, user=user, is_owner=True)
url = reverse("tasks-downvote", args=(task.id,))
client.login(user)
response = client.post(url)
assert response.status_code == 200
def test_list_task_voters(client):
user = f.UserFactory.create()
task = f.create_task(owner=user)
f.MembershipFactory.create(project=task.project, user=user, is_owner=True)
f.VoteFactory.create(content_object=task, user=user)
url = reverse("task-voters-list", args=(task.id,))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data[0]['id'] == user.id
def test_get_task_voter(client):
user = f.UserFactory.create()
task = f.create_task(owner=user)
f.MembershipFactory.create(project=task.project, user=user, is_owner=True)
vote = f.VoteFactory.create(content_object=task, user=user)
url = reverse("task-voters-detail", args=(task.id, vote.user.id))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['id'] == vote.user.id
def test_get_task_votes(client):
user = f.UserFactory.create()
task = f.create_task(owner=user)
f.MembershipFactory.create(project=task.project, user=user, is_owner=True)
url = reverse("tasks-detail", args=(task.id,))
f.VotesFactory.create(content_object=task, count=5)
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['votes'] == 5
def test_get_task_is_voted(client):
user = f.UserFactory.create()
task = f.create_task(owner=user)
f.MembershipFactory.create(project=task.project, user=user, is_owner=True)
f.VotesFactory.create(content_object=task)
url_detail = reverse("tasks-detail", args=(task.id,))
url_upvote = reverse("tasks-upvote", args=(task.id,))
url_downvote = reverse("tasks-downvote", args=(task.id,))
client.login(user)
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['votes'] == 0
assert response.data['is_voted'] == False
response = client.post(url_upvote)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['votes'] == 1
assert response.data['is_voted'] == True
response = client.post(url_downvote)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['votes'] == 0
assert response.data['is_voted'] == False

View File

@ -0,0 +1,122 @@
# Copyright (C) 2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2015 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2015 David Barragán <bameda@dbarragan.com>
# Copyright (C) 2015 Anler Hernández <hello@anler.me>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from django.core.urlresolvers import reverse
from .. import factories as f
pytestmark = pytest.mark.django_db
def test_upvote_user_story(client):
user = f.UserFactory.create()
user_story = f.create_userstory(owner=user)
f.MembershipFactory.create(project=user_story.project, user=user, is_owner=True)
url = reverse("userstories-upvote", args=(user_story.id,))
client.login(user)
response = client.post(url)
assert response.status_code == 200
def test_downvote_user_story(client):
user = f.UserFactory.create()
user_story = f.create_userstory(owner=user)
f.MembershipFactory.create(project=user_story.project, user=user, is_owner=True)
url = reverse("userstories-downvote", args=(user_story.id,))
client.login(user)
response = client.post(url)
assert response.status_code == 200
def test_list_user_story_voters(client):
user = f.UserFactory.create()
user_story = f.create_userstory(owner=user)
f.MembershipFactory.create(project=user_story.project, user=user, is_owner=True)
f.VoteFactory.create(content_object=user_story, user=user)
url = reverse("userstory-voters-list", args=(user_story.id,))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data[0]['id'] == user.id
def test_get_userstory_voter(client):
user = f.UserFactory.create()
user_story = f.create_userstory(owner=user)
f.MembershipFactory.create(project=user_story.project, user=user, is_owner=True)
vote = f.VoteFactory.create(content_object=user_story, user=user)
url = reverse("userstory-voters-detail", args=(user_story.id, vote.user.id))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['id'] == vote.user.id
def test_get_user_story_votes(client):
user = f.UserFactory.create()
user_story = f.create_userstory(owner=user)
f.MembershipFactory.create(project=user_story.project, user=user, is_owner=True)
url = reverse("userstories-detail", args=(user_story.id,))
f.VotesFactory.create(content_object=user_story, count=5)
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['votes'] == 5
def test_get_user_story_is_voted(client):
user = f.UserFactory.create()
user_story = f.create_userstory(owner=user)
f.MembershipFactory.create(project=user_story.project, user=user, is_owner=True)
f.VotesFactory.create(content_object=user_story)
url_detail = reverse("userstories-detail", args=(user_story.id,))
url_upvote = reverse("userstories-upvote", args=(user_story.id,))
url_downvote = reverse("userstories-downvote", args=(user_story.id,))
client.login(user)
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['votes'] == 0
assert response.data['is_voted'] == False
response = client.post(url_upvote)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['votes'] == 1
assert response.data['is_voted'] == True
response = client.post(url_downvote)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['votes'] == 0
assert response.data['is_voted'] == False

View File

@ -16,7 +16,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.db.models import signals from django.db.models import signals
from taiga.base.utils import json
def signals_switch(): def signals_switch():
@ -69,9 +68,9 @@ def helper_test_http_method(client, method, url, data, users, after_each_request
def helper_test_http_method_and_count(client, method, url, data, users, after_each_request=None): def helper_test_http_method_and_count(client, method, url, data, users, after_each_request=None):
responses = _helper_test_http_method_responses(client, method, url, data, users, after_each_request) responses = _helper_test_http_method_responses(client, method, url, data, users, after_each_request)
return list(map(lambda r: (r.status_code, len(json.loads(r.content.decode('utf-8')))), responses)) return list(map(lambda r: (r.status_code, len(r.data) if isinstance(r.data, list) and 200 <= r.status_code < 300 else 0), responses))
def helper_test_http_method_and_keys(client, method, url, data, users, after_each_request=None): def helper_test_http_method_and_keys(client, method, url, data, users, after_each_request=None):
responses = _helper_test_http_method_responses(client, method, url, data, users, after_each_request) responses = _helper_test_http_method_responses(client, method, url, data, users, after_each_request)
return list(map(lambda r: (r.status_code, set(json.loads(r.content.decode('utf-8')).keys())), responses)) return list(map(lambda r: (r.status_code, set(r.data.keys() if isinstance(r.data, dict) and 200 <= r.status_code < 300 else [])), responses))