Improve votes module
parent
cf63031844
commit
44eee5212a
|
@ -4,13 +4,15 @@
|
|||
## 1.9.0 ??? (unreleased)
|
||||
|
||||
### 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.
|
||||
- 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.
|
||||
- Add custom videoconference system.
|
||||
- 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.
|
||||
- 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.
|
||||
- Add polish (pl) translation.
|
||||
- Add portuguese (Brazil) (pt_BR) translation.
|
||||
|
|
|
@ -32,7 +32,6 @@ USER_PERMISSIONS = [
|
|||
('view_milestones', _('View milestones')),
|
||||
('view_us', _('View user stories')),
|
||||
('view_issues', _('View issues')),
|
||||
('vote_issues', _('Vote issues')),
|
||||
('view_tasks', _('View tasks')),
|
||||
('view_wiki_pages', _('View wiki pages')),
|
||||
('view_wiki_links', _('View wiki links')),
|
||||
|
@ -41,15 +40,20 @@ USER_PERMISSIONS = [
|
|||
('add_comments_to_us', _('Add comments to user stories')),
|
||||
('add_comments_to_task', _('Add comments to tasks')),
|
||||
('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')),
|
||||
('modify_wiki_page', _('Modify wiki page')),
|
||||
('add_wiki_link', _('Add 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 = [
|
||||
('view_project', _('View project')),
|
||||
('star_project', _('Star project')),
|
||||
# Milestone permissions
|
||||
('view_milestones', _('View milestones')),
|
||||
('add_milestone', _('Add milestone')),
|
||||
|
@ -60,17 +64,19 @@ MEMBERS_PERMISSIONS = [
|
|||
('add_us', _('Add user story')),
|
||||
('modify_us', _('Modify user story')),
|
||||
('delete_us', _('Delete user story')),
|
||||
('vote_us', _('Vote user story')),
|
||||
# Task permissions
|
||||
('view_tasks', _('View tasks')),
|
||||
('add_task', _('Add task')),
|
||||
('modify_task', _('Modify task')),
|
||||
('delete_task', _('Delete task')),
|
||||
('vote_task', _('Vote task')),
|
||||
# Issue permissions
|
||||
('view_issues', _('View issues')),
|
||||
('vote_issues', _('Vote issues')),
|
||||
('add_issue', _('Add issue')),
|
||||
('modify_issue', _('Modify issue')),
|
||||
('delete_issue', _('Delete issue')),
|
||||
('vote_issue', _('Vote issue')),
|
||||
# Wiki page permissions
|
||||
('view_wiki_pages', _('View wiki pages')),
|
||||
('add_wiki_page', _('Add wiki page')),
|
||||
|
|
|
@ -44,15 +44,14 @@ from . import models
|
|||
from . import permissions
|
||||
from . import services
|
||||
|
||||
from .votes import serializers as votes_serializers
|
||||
from .votes import services as votes_service
|
||||
from .votes.utils import attach_votescount_to_queryset
|
||||
from .votes.mixins.viewsets import StarredResourceMixin, VotersViewSetMixin
|
||||
|
||||
######################################################
|
||||
## Project
|
||||
######################################################
|
||||
|
||||
class ProjectViewSet(HistoryResourceMixin, ModelCrudViewSet):
|
||||
class ProjectViewSet(StarredResourceMixin, HistoryResourceMixin, ModelCrudViewSet):
|
||||
queryset = models.Project.objects.all()
|
||||
serializer_class = serializers.ProjectDetailSerializer
|
||||
admin_serializer_class = serializers.ProjectDetailAdminSerializer
|
||||
list_serializer_class = serializers.ProjectSerializer
|
||||
|
@ -61,6 +60,10 @@ class ProjectViewSet(HistoryResourceMixin, ModelCrudViewSet):
|
|||
filter_fields = (('member', 'members'),)
|
||||
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"])
|
||||
def bulk_update_order(self, request, **kwargs):
|
||||
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)
|
||||
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):
|
||||
if self.action == "list":
|
||||
return self.list_serializer_class
|
||||
|
@ -166,29 +165,6 @@ class ProjectViewSet(HistoryResourceMixin, ModelCrudViewSet):
|
|||
self.check_permissions(request, "tags_colors", project)
|
||||
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"])
|
||||
def create_template(self, request, **kwargs):
|
||||
template_name = request.DATA.get('template_name', None)
|
||||
|
@ -287,6 +263,10 @@ class ProjectViewSet(HistoryResourceMixin, ModelCrudViewSet):
|
|||
return response.NoContent()
|
||||
|
||||
|
||||
class ProjectFansViewSet(VotersViewSetMixin, ModelListViewSet):
|
||||
permission_classes = (permissions.ProjectFansPermission,)
|
||||
resource_model = models.Project
|
||||
|
||||
|
||||
######################################################
|
||||
## Custom values for selectors
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
from django.utils.translation import ugettext as _
|
||||
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 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.milestones.models import Milestone
|
||||
from taiga.projects.votes.utils import attach_votescount_to_queryset
|
||||
from taiga.projects.votes import services as votes_service
|
||||
from taiga.projects.votes import serializers as votes_serializers
|
||||
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
|
||||
|
||||
from . import models
|
||||
from . import services
|
||||
from . import permissions
|
||||
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, )
|
||||
filter_backends = (filters.CanViewIssuesFilterBackend,
|
||||
filters.OwnersFilter,
|
||||
|
@ -139,10 +140,9 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
|||
return super().update(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
qs = models.Issue.objects.all()
|
||||
qs = super().get_queryset()
|
||||
qs = qs.prefetch_related("attachments")
|
||||
qs = attach_votescount_to_queryset(qs, as_field="votes_count")
|
||||
return qs
|
||||
return self.attach_votes_attrs_to_queryset(qs)
|
||||
|
||||
def pre_save(self, obj):
|
||||
if not obj.id:
|
||||
|
@ -237,51 +237,7 @@ class IssueViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
|||
|
||||
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)
|
||||
|
||||
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, )
|
||||
|
||||
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)
|
||||
class IssueVotersViewSet(VotersViewSetMixin, ModelListViewSet):
|
||||
permission_classes = (permissions.IssueVotersPermission,)
|
||||
resource_model = models.Issue
|
||||
|
|
|
@ -31,10 +31,10 @@ class IssuePermission(TaigaResourcePermission):
|
|||
list_perms = AllowAny()
|
||||
filters_data_perms = AllowAny()
|
||||
csv_perms = AllowAny()
|
||||
upvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
|
||||
downvote_perms = IsAuthenticated() & HasProjectPerm('vote_issues')
|
||||
bulk_create_perms = HasProjectPerm('add_issue')
|
||||
delete_comment_perms= HasProjectPerm('modify_issue')
|
||||
upvote_perms = IsAuthenticated() & HasProjectPerm('view_issues')
|
||||
downvote_perms = IsAuthenticated() & HasProjectPerm('view_issues')
|
||||
|
||||
|
||||
class HasIssueIdUrlParam(PermissionComponent):
|
||||
|
@ -49,8 +49,4 @@ class IssueVotersPermission(TaigaResourcePermission):
|
|||
enought_perms = IsProjectOwner() | IsSuperUser()
|
||||
global_perms = None
|
||||
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')
|
||||
|
|
|
@ -19,17 +19,18 @@ from taiga.base.fields import TagsField
|
|||
from taiga.base.fields import PgArrayField
|
||||
from taiga.base.neighbors import NeighborsSerializerMixin
|
||||
|
||||
|
||||
from taiga.mdrender.service import render as mdrender
|
||||
from taiga.projects.validators import ProjectExistsValidator
|
||||
from taiga.projects.notifications.validators import WatchersValidator
|
||||
from taiga.projects.serializers import BasicIssueStatusSerializer
|
||||
from taiga.projects.votes.mixins.serializers import VotedResourceSerializerMixin
|
||||
|
||||
from taiga.users.serializers import UserBasicInfoSerializer
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class IssueSerializer(WatchersValidator, serializers.ModelSerializer):
|
||||
class IssueSerializer(WatchersValidator, VotedResourceSerializerMixin, serializers.ModelSerializer):
|
||||
tags = TagsField(required=False)
|
||||
external_reference = PgArrayField(required=False)
|
||||
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")
|
||||
blocked_note_html = serializers.SerializerMethodField("get_blocked_note_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)
|
||||
assigned_to_extra_info = UserBasicInfoSerializer(source="assigned_to", 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):
|
||||
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 Meta:
|
||||
|
|
|
@ -54,19 +54,25 @@ class ProjectPermission(TaigaResourcePermission):
|
|||
list_perms = AllowAny()
|
||||
stats_perms = HasProjectPerm('view_project')
|
||||
member_stats_perms = HasProjectPerm('view_project')
|
||||
issues_stats_perms = HasProjectPerm('view_project')
|
||||
regenerate_userstories_csv_uuid_perms = IsProjectOwner()
|
||||
regenerate_issues_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_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()
|
||||
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):
|
||||
retrieve_perms = HasProjectPerm('view_project')
|
||||
create_perms = IsProjectOwner()
|
||||
|
|
|
@ -40,7 +40,7 @@ from .validators import ProjectExistsValidator
|
|||
from .custom_attributes.serializers import UserStoryCustomAttributeSerializer
|
||||
from .custom_attributes.serializers import TaskCustomAttributeSerializer
|
||||
from .custom_attributes.serializers import IssueCustomAttributeSerializer
|
||||
|
||||
from .votes.mixins.serializers import StarredResourceSerializerMixin
|
||||
|
||||
######################################################
|
||||
## Custom values for selectors
|
||||
|
@ -305,11 +305,10 @@ class ProjectMemberSerializer(serializers.ModelSerializer):
|
|||
## Projects
|
||||
######################################################
|
||||
|
||||
class ProjectSerializer(serializers.ModelSerializer):
|
||||
class ProjectSerializer(StarredResourceSerializerMixin, serializers.ModelSerializer):
|
||||
tags = TagsField(default=[], required=False)
|
||||
anon_permissions = PgArrayField(required=False)
|
||||
public_permissions = PgArrayField(required=False)
|
||||
stars = serializers.SerializerMethodField("get_stars_number")
|
||||
my_permissions = serializers.SerializerMethodField("get_my_permissions")
|
||||
i_am_owner = serializers.SerializerMethodField("get_i_am_owner")
|
||||
tags_colors = TagsColorsField(required=False)
|
||||
|
@ -321,10 +320,6 @@ class ProjectSerializer(serializers.ModelSerializer):
|
|||
exclude = ("last_us_ref", "last_task_ref", "last_issue_ref",
|
||||
"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):
|
||||
if "request" in self.context:
|
||||
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 exceptions as exc
|
||||
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 django.http import HttpResponse
|
||||
|
||||
from taiga.projects.notifications.mixins import WatchedResourceMixin
|
||||
from taiga.projects.history.mixins import HistoryResourceMixin
|
||||
from taiga.projects.occ import OCCResourceMixin
|
||||
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
|
||||
|
||||
|
||||
from . import models
|
||||
|
@ -35,8 +36,9 @@ from . import serializers
|
|||
from . import services
|
||||
|
||||
|
||||
class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
|
||||
model = models.Task
|
||||
class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||
ModelCrudViewSet):
|
||||
queryset = models.Task.objects.all()
|
||||
permission_classes = (permissions.TaskPermission,)
|
||||
filter_backends = (filters.CanViewTasksFilterBackend,)
|
||||
filter_fields = ["user_story", "milestone", "project", "assigned_to",
|
||||
|
@ -82,6 +84,9 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
|||
|
||||
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):
|
||||
if obj.user_story:
|
||||
|
@ -165,3 +170,8 @@ class TaskViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
|||
@list_route(methods=["POST"])
|
||||
def bulk_update_us_order(self, 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/>.
|
||||
|
||||
from taiga.base.api.permissions import (TaigaResourcePermission, HasProjectPerm,
|
||||
IsProjectOwner, AllowAny, IsSuperUser)
|
||||
IsAuthenticated, IsProjectOwner, AllowAny,
|
||||
IsSuperUser)
|
||||
|
||||
|
||||
class TaskPermission(TaigaResourcePermission):
|
||||
|
@ -30,3 +31,12 @@ class TaskPermission(TaigaResourcePermission):
|
|||
csv_perms = AllowAny()
|
||||
bulk_create_perms = HasProjectPerm('add_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.notifications.validators import WatchersValidator
|
||||
from taiga.projects.serializers import BasicTaskStatusSerializerSerializer
|
||||
from taiga.projects.votes.mixins.serializers import VotedResourceSerializerMixin
|
||||
|
||||
from taiga.users.serializers import UserBasicInfoSerializer
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class TaskSerializer(WatchersValidator, serializers.ModelSerializer):
|
||||
class TaskSerializer(WatchersValidator, VotedResourceSerializerMixin, serializers.ModelSerializer):
|
||||
tags = TagsField(required=False, default=[])
|
||||
external_reference = PgArrayField(required=False)
|
||||
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 status
|
||||
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.projects.notifications.mixins import WatchedResourceMixin
|
||||
from taiga.projects.history.mixins import HistoryResourceMixin
|
||||
from taiga.projects.occ import OCCResourceMixin
|
||||
|
||||
from taiga.projects.models import Project, UserStoryStatus
|
||||
from taiga.projects.history.services import take_snapshot
|
||||
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
|
||||
|
||||
from . import models
|
||||
from . import permissions
|
||||
|
@ -44,8 +44,9 @@ from . import serializers
|
|||
from . import services
|
||||
|
||||
|
||||
class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
|
||||
model = models.UserStory
|
||||
class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
|
||||
ModelCrudViewSet):
|
||||
queryset = models.UserStory.objects.all()
|
||||
permission_classes = (permissions.UserStoryPermission,)
|
||||
filter_backends = (filters.CanViewUsFilterBackend,
|
||||
filters.OwnersFilter,
|
||||
|
@ -109,13 +110,13 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
|
|||
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.model.objects.all()
|
||||
qs = super().get_queryset()
|
||||
qs = qs.prefetch_related("role_points",
|
||||
"role_points__points",
|
||||
"role_points__role",
|
||||
"watchers")
|
||||
qs = qs.select_related("milestone", "project")
|
||||
return qs
|
||||
return self.attach_votes_attrs_to_queryset(qs)
|
||||
|
||||
def pre_save(self, obj):
|
||||
# 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)
|
||||
|
||||
return response
|
||||
|
||||
class UserStoryVotersViewSet(VotersViewSetMixin, ModelListViewSet):
|
||||
permission_classes = (permissions.UserStoryVotersPermission,)
|
||||
resource_model = models.UserStory
|
||||
|
|
|
@ -30,3 +30,12 @@ class UserStoryPermission(TaigaResourcePermission):
|
|||
csv_perms = AllowAny()
|
||||
bulk_create_perms = IsAuthenticated() & (HasProjectPerm('add_us_to_project') | HasProjectPerm('add_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.notifications.validators import WatchersValidator
|
||||
from taiga.projects.serializers import BasicUserStoryStatusSerializer
|
||||
from taiga.projects.votes.mixins.serializers import VotedResourceSerializerMixin
|
||||
|
||||
from taiga.users.serializers import UserBasicInfoSerializer
|
||||
|
||||
from . import models
|
||||
|
@ -42,7 +44,7 @@ class RolePointsField(serializers.WritableField):
|
|||
return json.loads(obj)
|
||||
|
||||
|
||||
class UserStorySerializer(WatchersValidator, serializers.ModelSerializer):
|
||||
class UserStorySerializer(WatchersValidator, VotedResourceSerializerMixin, serializers.ModelSerializer):
|
||||
tags = TagsField(default=[], required=False)
|
||||
external_reference = PgArrayField(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/>.
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes import generic
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.contenttypes import generic
|
||||
|
||||
|
||||
class Votes(models.Model):
|
||||
content_type = models.ForeignKey("contenttypes.ContentType")
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = generic.GenericForeignKey("content_type", "object_id")
|
||||
count = models.PositiveIntegerField(default=0)
|
||||
count = models.PositiveIntegerField(null=False, blank=False, default=0, verbose_name=_("count"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Votes")
|
||||
|
@ -44,10 +44,12 @@ class Votes(models.Model):
|
|||
|
||||
class Vote(models.Model):
|
||||
content_type = models.ForeignKey("contenttypes.ContentType")
|
||||
object_id = models.PositiveIntegerField(null=False)
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = generic.GenericForeignKey("content_type", "object_id")
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
||||
related_name="votes", verbose_name=_("votes"))
|
||||
related_name="votes", verbose_name=_("user"))
|
||||
created_date = models.DateTimeField(null=False, blank=False, auto_now_add=True,
|
||||
verbose_name=_("created date"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Vote")
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
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.
|
||||
|
||||
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
|
||||
type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(model)
|
||||
sql = ("SELECT coalesce(votes_votes.count, 0) FROM votes_votes "
|
||||
"WHERE votes_votes.content_type_id = {type_id} AND votes_votes.object_id = {tbl}.id")
|
||||
sql = ("""SELECT coalesce(votes_votes.count, 0)
|
||||
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)
|
||||
qs = queryset.extra(select={as_field: sql})
|
||||
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 Jesús Espino <jespinog@gmail.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
|
||||
from taiga.projects.api import ProjectViewSet
|
||||
from taiga.projects.api import ProjectFansViewSet
|
||||
from taiga.projects.api import MembershipViewSet
|
||||
from taiga.projects.api import InvitationViewSet
|
||||
from taiga.projects.api import UserStoryStatusViewSet
|
||||
|
@ -61,6 +61,7 @@ from taiga.projects.api import SeverityViewSet
|
|||
from taiga.projects.api import ProjectTemplateViewSet
|
||||
|
||||
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"memberships", MembershipViewSet, base_name="memberships")
|
||||
router.register(r"invitations", InvitationViewSet, base_name="invitations")
|
||||
|
@ -124,20 +125,26 @@ router.register(r"wiki/attachments", WikiAttachmentViewSet, base_name="wiki-atta
|
|||
# Project components
|
||||
from taiga.projects.milestones.api import MilestoneViewSet
|
||||
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 TaskVotersViewSet
|
||||
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
|
||||
|
||||
router.register(r"milestones", MilestoneViewSet, base_name="milestones")
|
||||
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/(?P<resource_id>\d+)/voters", TaskVotersViewSet, base_name="task-voters")
|
||||
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-links", WikiLinkViewSet, base_name="wiki-links")
|
||||
|
||||
|
||||
|
||||
|
||||
# History & Components
|
||||
from taiga.projects.history.api import UserStoryHistory
|
||||
from taiga.projects.history.api import TaskHistory
|
||||
|
|
|
@ -224,15 +224,6 @@ class UsersViewSet(ModelCrudViewSet):
|
|||
user_data = self.admin_serializer_class(request.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
|
||||
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)
|
||||
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 == [401, 403, 403, 200, 200]
|
||||
assert results == [404, 404, 404, 200, 200]
|
||||
|
||||
|
||||
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)
|
||||
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 == [401, 403, 403, 200, 200]
|
||||
assert results == [404, 404, 404, 200, 200]
|
||||
|
||||
|
||||
def test_issue_voters_list(client, data):
|
||||
public_url = reverse('issue-voters-list', kwargs={"issue_id": data.public_issue.pk})
|
||||
private_url1 = reverse('issue-voters-list', kwargs={"issue_id": data.private_issue1.pk})
|
||||
private_url2 = reverse('issue-voters-list', kwargs={"issue_id": data.private_issue2.pk})
|
||||
public_url = reverse('issue-voters-list', kwargs={"resource_id": data.public_issue.pk})
|
||||
private_url1 = reverse('issue-voters-list', kwargs={"resource_id": data.private_issue1.pk})
|
||||
private_url2 = reverse('issue-voters-list', kwargs={"resource_id": data.private_issue2.pk})
|
||||
|
||||
users = [
|
||||
None,
|
||||
|
@ -523,21 +519,22 @@ def test_issue_voters_list(client, data):
|
|||
|
||||
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_issue_voters_retrieve(client, data):
|
||||
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)
|
||||
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)
|
||||
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 = [
|
||||
None,
|
||||
|
@ -549,10 +546,8 @@ def test_issue_voters_retrieve(client, data):
|
|||
|
||||
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]
|
||||
|
||||
|
|
|
@ -198,6 +198,25 @@ def test_project_action_stats(client, data):
|
|||
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):
|
||||
public_url = reverse('projects-star', kwargs={"pk": data.public_project.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]
|
||||
|
||||
|
||||
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_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})
|
||||
def test_project_fans_list(client, data):
|
||||
public_url = reverse('project-fans-list', kwargs={"resource_id": data.public_project.pk})
|
||||
private1_url = reverse('project-fans-list', kwargs={"resource_id": data.private_project1.pk})
|
||||
private2_url = reverse('project-fans-list', kwargs={"resource_id": data.private_project2.pk})
|
||||
|
||||
users = [
|
||||
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)
|
||||
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)
|
||||
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):
|
||||
url1 = reverse('users-starred', kwargs={"pk": data.project_member_without_perms.pk})
|
||||
url2 = reverse('users-starred', kwargs={"pk": data.project_member_with_perms.pk})
|
||||
url3 = reverse('users-starred', kwargs={"pk": data.project_owner.pk})
|
||||
def test_project_fans_retrieve(client, data):
|
||||
public_url = reverse('project-fans-detail', kwargs={"resource_id": data.public_project.pk,
|
||||
"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 = [
|
||||
None,
|
||||
|
@ -289,12 +292,12 @@ def test_user_action_starred(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
results = helper_test_http_method_and_count(client, 'get', url1, None, users)
|
||||
assert results == [(200, 0), (200, 0), (200, 0), (200, 0), (200, 0)]
|
||||
results = helper_test_http_method_and_count(client, 'get', url2, None, users)
|
||||
assert results == [(200, 3), (200, 3), (200, 3), (200, 3), (200, 3)]
|
||||
results = helper_test_http_method_and_count(client, 'get', url3, None, users)
|
||||
assert results == [(200, 3), (200, 3), (200, 3), (200, 3), (200, 3)]
|
||||
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', private1_url, None, users)
|
||||
assert results == [200, 200, 200, 200, 200]
|
||||
results = helper_test_http_method(client, 'get', private2_url, None, users)
|
||||
assert results == [401, 403, 403, 200, 200]
|
||||
|
||||
|
||||
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.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||
from taiga.projects.votes.services import add_vote
|
||||
|
||||
from unittest import mock
|
||||
|
||||
|
@ -416,6 +417,96 @@ def test_task_action_bulk_create(client, data):
|
|||
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):
|
||||
url = reverse('tasks-csv')
|
||||
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.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||
from taiga.projects.votes.services import add_vote
|
||||
|
||||
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)
|
||||
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):
|
||||
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) 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>
|
||||
# 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
|
||||
|
@ -51,8 +51,8 @@ def test_list_issue_voters(client):
|
|||
user = f.UserFactory.create()
|
||||
issue = f.create_issue(owner=user)
|
||||
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)
|
||||
url = reverse("issue-voters-list", args=(issue.id,))
|
||||
|
||||
client.login(user)
|
||||
response = client.get(url)
|
||||
|
@ -60,7 +60,6 @@ def test_list_issue_voters(client):
|
|||
assert response.status_code == 200
|
||||
assert response.data[0]['id'] == user.id
|
||||
|
||||
|
||||
def test_get_issue_voter(client):
|
||||
user = f.UserFactory.create()
|
||||
issue = f.create_issue(owner=user)
|
||||
|
@ -74,7 +73,6 @@ def test_get_issue_voter(client):
|
|||
assert response.status_code == 200
|
||||
assert response.data['id'] == vote.user.id
|
||||
|
||||
|
||||
def test_get_issue_votes(client):
|
||||
user = f.UserFactory.create()
|
||||
issue = f.create_issue(owner=user)
|
||||
|
@ -88,3 +86,36 @@ def test_get_issue_votes(client):
|
|||
|
||||
assert response.status_code == 200
|
||||
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/>.
|
||||
|
||||
from django.db.models import signals
|
||||
from taiga.base.utils import json
|
||||
|
||||
|
||||
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):
|
||||
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):
|
||||
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