Improve votes module
parent
cf63031844
commit
44eee5212a
|
@ -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.
|
||||||
|
|
|
@ -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')),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
]
|
|
@ -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")
|
|
@ -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)
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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))
|
||||||
|
|
Loading…
Reference in New Issue