From da8024141ba24b91c2d1cf67b8f634d4d55deb0a Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 30 Sep 2015 15:06:15 +0200 Subject: [PATCH] Refactoring likes, votes, watchers and favourites names --- taiga/projects/api.py | 8 +- taiga/projects/history/services.py | 2 +- taiga/projects/issues/api.py | 2 +- taiga/projects/issues/serializers.py | 6 +- taiga/projects/notifications/mixins.py | 28 ++- taiga/projects/notifications/utils.py | 34 ++- taiga/projects/serializers.py | 8 +- taiga/projects/tasks/serializers.py | 6 +- taiga/projects/userstories/api.py | 2 +- taiga/projects/userstories/serializers.py | 6 +- taiga/projects/votes/mixins/serializers.py | 30 ++- taiga/projects/votes/mixins/viewsets.py | 6 +- taiga/projects/votes/services.py | 1 - taiga/projects/votes/utils.py | 8 +- taiga/users/api.py | 50 +++- taiga/users/permissions.py | 3 +- taiga/users/serializers.py | 33 ++- taiga/users/services.py | 83 +++---- taiga/webhooks/serializers.py | 8 +- .../test_users_resources.py | 16 +- tests/integration/test_star_projects.py | 123 ---------- tests/integration/test_users.py | 228 ++++++++++++++---- tests/integration/test_vote_issues.py | 14 +- tests/integration/test_vote_tasks.py | 14 +- tests/integration/test_vote_userstories.py | 14 +- tests/integration/test_watch_issues.py | 9 +- tests/integration/test_watch_milestones.py | 16 +- tests/integration/test_watch_projects.py | 17 +- tests/integration/test_watch_tasks.py | 9 +- tests/integration/test_watch_userstories.py | 9 +- tests/integration/test_watch_wikipages.py | 16 +- 31 files changed, 459 insertions(+), 350 deletions(-) delete mode 100644 tests/integration/test_star_projects.py diff --git a/taiga/projects/api.py b/taiga/projects/api.py index 97e0b0a8..78993322 100644 --- a/taiga/projects/api.py +++ b/taiga/projects/api.py @@ -34,8 +34,8 @@ from taiga.projects.history.mixins import HistoryResourceMixin from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin from taiga.projects.notifications.choices import NotifyLevel from taiga.projects.notifications.utils import ( - attach_project_watchers_attrs_to_queryset, - attach_project_is_watched_to_queryset, + attach_project_total_watchers_attrs_to_queryset, + attach_project_is_watcher_to_queryset, attach_notify_level_to_project_queryset) from taiga.projects.mixins.ordering import BulkUpdateOrderMixin @@ -69,9 +69,9 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, ModelCrudViewSet) def get_queryset(self): qs = super().get_queryset() qs = self.attach_votes_attrs_to_queryset(qs) - qs = attach_project_watchers_attrs_to_queryset(qs) + qs = attach_project_total_watchers_attrs_to_queryset(qs) if self.request.user.is_authenticated(): - qs = attach_project_is_watched_to_queryset(qs, self.request.user) + qs = attach_project_is_watcher_to_queryset(qs, self.request.user) qs = attach_notify_level_to_project_queryset(qs, self.request.user) return qs diff --git a/taiga/projects/history/services.py b/taiga/projects/history/services.py index 593eebad..317a9374 100644 --- a/taiga/projects/history/services.py +++ b/taiga/projects/history/services.py @@ -331,7 +331,7 @@ def take_snapshot(obj:object, *, comment:str="", user=None, delete:bool=False): "is_hidden": is_hidden, "is_snapshot": need_real_snapshot, } - + return entry_model.objects.create(**kwargs) diff --git a/taiga/projects/issues/api.py b/taiga/projects/issues/api.py index e1259e10..e187c3df 100644 --- a/taiga/projects/issues/api.py +++ b/taiga/projects/issues/api.py @@ -76,7 +76,7 @@ class IssueViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, W "owner", "assigned_to", "subject", - "votes_count") + "total_voters") def get_serializer_class(self, *args, **kwargs): if self.action in ["retrieve", "by_ref"]: diff --git a/taiga/projects/issues/serializers.py b/taiga/projects/issues/serializers.py index 64c83f17..f7978861 100644 --- a/taiga/projects/issues/serializers.py +++ b/taiga/projects/issues/serializers.py @@ -23,15 +23,15 @@ 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.notifications.mixins import WatchedResourceModelSerializer -from taiga.projects.votes.mixins.serializers import VotedResourceSerializerMixin +from taiga.projects.notifications.mixins import EditableWatchedResourceModelSerializer +from taiga.projects.votes.mixins.serializers import VoteResourceSerializerMixin from taiga.users.serializers import UserBasicInfoSerializer from . import models -class IssueSerializer(WatchersValidator, VotedResourceSerializerMixin, WatchedResourceModelSerializer, serializers.ModelSerializer): +class IssueSerializer(WatchersValidator, VoteResourceSerializerMixin, EditableWatchedResourceModelSerializer, serializers.ModelSerializer): tags = TagsField(required=False) external_reference = PgArrayField(required=False) is_closed = serializers.Field(source="is_closed") diff --git a/taiga/projects/notifications/mixins.py b/taiga/projects/notifications/mixins.py index 03082f3b..fe1812f4 100644 --- a/taiga/projects/notifications/mixins.py +++ b/taiga/projects/notifications/mixins.py @@ -29,7 +29,10 @@ from taiga.base.api import serializers from taiga.base.api.utils import get_object_or_404 from taiga.base.fields import WatchersField from taiga.projects.notifications import services -from taiga.projects.notifications.utils import attach_watchers_to_queryset, attach_is_watched_to_queryset +from taiga.projects.notifications.utils import (attach_watchers_to_queryset, + attach_is_watcher_to_queryset, + attach_total_watchers_to_queryset) + from taiga.users.models import User from . import models from . serializers import WatcherSerializer @@ -50,8 +53,9 @@ class WatchedResourceMixin: def attach_watchers_attrs_to_queryset(self, queryset): qs = attach_watchers_to_queryset(queryset) + qs = attach_total_watchers_to_queryset(queryset) if self.request.user.is_authenticated(): - qs = attach_is_watched_to_queryset(qs, self.request.user) + qs = attach_is_watcher_to_queryset(qs, self.request.user) return qs @@ -178,12 +182,20 @@ class WatchedModelMixin(object): class WatchedResourceModelSerializer(serializers.ModelSerializer): - is_watched = serializers.SerializerMethodField("get_is_watched") - watchers = WatchersField(required=False) + is_watcher = serializers.SerializerMethodField("get_is_watcher") + total_watchers = serializers.SerializerMethodField("get_total_watchers") - def get_is_watched(self, obj): - # The "is_watched" attribute is attached in the get_queryset of the viewset. - return getattr(obj, "is_watched", False) or False + def get_is_watcher(self, obj): + # The "is_watcher" attribute is attached in the get_queryset of the viewset. + return getattr(obj, "is_watcher", False) or False + + def get_total_watchers(self, obj): + # The "total_watchers" attribute is attached in the get_queryset of the viewset. + return getattr(obj, "total_watchers", 0) or 0 + + +class EditableWatchedResourceModelSerializer(WatchedResourceModelSerializer): + watchers = WatchersField(required=False) def restore_object(self, attrs, instance=None): #watchers is not a field from the model but can be attached in the get_queryset of the viewset. @@ -223,7 +235,7 @@ class WatchedResourceModelSerializer(serializers.ModelSerializer): return super(WatchedResourceModelSerializer, self).to_native(obj) def save(self, **kwargs): - obj = super(WatchedResourceModelSerializer, self).save(**kwargs) + obj = super(EditableWatchedResourceModelSerializer, self).save(**kwargs) self.fields["watchers"] = WatchersField(required=False) obj.watchers = [user.id for user in obj.get_watchers()] return obj diff --git a/taiga/projects/notifications/utils.py b/taiga/projects/notifications/utils.py index 39e6eaff..510ab76a 100644 --- a/taiga/projects/notifications/utils.py +++ b/taiga/projects/notifications/utils.py @@ -40,8 +40,8 @@ def attach_watchers_to_queryset(queryset, as_field="watchers"): return qs -def attach_is_watched_to_queryset(queryset, user, as_field="is_watched"): - """Attach is_watched boolean to each object of the queryset. +def attach_is_watcher_to_queryset(queryset, user, as_field="is_watcher"): + """Attach is_watcher boolean to each object of the queryset. :param user: A users.User object model :param queryset: A Django queryset object. @@ -64,8 +64,28 @@ def attach_is_watched_to_queryset(queryset, user, as_field="is_watched"): return qs -def attach_project_is_watched_to_queryset(queryset, user, as_field="is_watched"): - """Attach is_watched boolean to each object of the projects queryset. +def attach_total_watchers_to_queryset(queryset, as_field="total_watchers"): + """Attach total_watchers boolean to each object of the queryset. + + :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 count(*) + FROM notifications_watched + WHERE notifications_watched.content_type_id = {type_id} + AND notifications_watched.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_project_is_watcher_to_queryset(queryset, user, as_field="is_watcher"): + """Attach is_watcher boolean to each object of the projects queryset. :param user: A users.User object model :param queryset: A Django projects queryset object. @@ -89,7 +109,7 @@ def attach_project_is_watched_to_queryset(queryset, user, as_field="is_watched") return qs -def attach_project_watchers_attrs_to_queryset(queryset, as_field="watchers"): +def attach_project_total_watchers_attrs_to_queryset(queryset, as_field="total_watchers"): """Attach watching user ids to each project of the queryset. :param queryset: A Django projects queryset object. @@ -100,10 +120,10 @@ def attach_project_watchers_attrs_to_queryset(queryset, as_field="watchers"): model = queryset.model type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(model) - sql = ("""SELECT array(SELECT user_id + sql = ("""SELECT count(user_id) FROM notifications_notifypolicy WHERE notifications_notifypolicy.project_id = {tbl}.id - AND notifications_notifypolicy.notify_level != {ignore_notify_level})""") + AND notifications_notifypolicy.notify_level != {ignore_notify_level}""") sql = sql.format(tbl=model._meta.db_table, ignore_notify_level=NotifyLevel.ignore) qs = queryset.extra(select={as_field: sql}) diff --git a/taiga/projects/serializers.py b/taiga/projects/serializers.py index 9fad7b7b..5443c0c3 100644 --- a/taiga/projects/serializers.py +++ b/taiga/projects/serializers.py @@ -25,8 +25,6 @@ from taiga.base.fields import PgArrayField from taiga.base.fields import TagsField from taiga.base.fields import TagsColorsField -from taiga.projects.notifications.validators import WatchersValidator - from taiga.users.services import get_photo_or_gravatar_url from taiga.users.serializers import UserSerializer from taiga.users.serializers import UserBasicInfoSerializer @@ -40,12 +38,12 @@ from taiga.projects.notifications import models as notify_models from . import models from . import services +from .notifications.mixins import WatchedResourceModelSerializer from .validators import ProjectExistsValidator from .custom_attributes.serializers import UserStoryCustomAttributeSerializer from .custom_attributes.serializers import TaskCustomAttributeSerializer from .custom_attributes.serializers import IssueCustomAttributeSerializer -from .notifications.mixins import WatchedResourceModelSerializer -from .votes.mixins.serializers import LikedResourceSerializerMixin +from .votes.mixins.serializers import FanResourceSerializerMixin ###################################################### ## Custom values for selectors @@ -310,7 +308,7 @@ class ProjectMemberSerializer(serializers.ModelSerializer): ## Projects ###################################################### -class ProjectSerializer(WatchersValidator, LikedResourceSerializerMixin, WatchedResourceModelSerializer, serializers.ModelSerializer): +class ProjectSerializer(FanResourceSerializerMixin, WatchedResourceModelSerializer, serializers.ModelSerializer): tags = TagsField(default=[], required=False) anon_permissions = PgArrayField(required=False) public_permissions = PgArrayField(required=False) diff --git a/taiga/projects/tasks/serializers.py b/taiga/projects/tasks/serializers.py index 221a188c..81964367 100644 --- a/taiga/projects/tasks/serializers.py +++ b/taiga/projects/tasks/serializers.py @@ -27,15 +27,15 @@ 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.notifications.mixins import WatchedResourceModelSerializer -from taiga.projects.votes.mixins.serializers import VotedResourceSerializerMixin +from taiga.projects.notifications.mixins import EditableWatchedResourceModelSerializer +from taiga.projects.votes.mixins.serializers import VoteResourceSerializerMixin from taiga.users.serializers import UserBasicInfoSerializer from . import models -class TaskSerializer(WatchersValidator, VotedResourceSerializerMixin, WatchedResourceModelSerializer, serializers.ModelSerializer): +class TaskSerializer(WatchersValidator, VoteResourceSerializerMixin, EditableWatchedResourceModelSerializer, serializers.ModelSerializer): tags = TagsField(required=False, default=[]) external_reference = PgArrayField(required=False) comment = serializers.SerializerMethodField("get_comment") diff --git a/taiga/projects/userstories/api.py b/taiga/projects/userstories/api.py index 4c4ebf59..9fce8fb0 100644 --- a/taiga/projects/userstories/api.py +++ b/taiga/projects/userstories/api.py @@ -70,7 +70,7 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi order_by_fields = ["backlog_order", "sprint_order", "kanban_order", - "votes_count"] + "total_voters"] # Specific filter used for filtering neighbor user stories _neighbor_tags_filter = filters.TagsFilter('neighbor_tags') diff --git a/taiga/projects/userstories/serializers.py b/taiga/projects/userstories/serializers.py index a461e57b..35801ba0 100644 --- a/taiga/projects/userstories/serializers.py +++ b/taiga/projects/userstories/serializers.py @@ -27,8 +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.notifications.mixins import WatchedResourceModelSerializer -from taiga.projects.votes.mixins.serializers import VotedResourceSerializerMixin +from taiga.projects.notifications.mixins import EditableWatchedResourceModelSerializer +from taiga.projects.votes.mixins.serializers import VoteResourceSerializerMixin from taiga.users.serializers import UserBasicInfoSerializer @@ -45,7 +45,7 @@ class RolePointsField(serializers.WritableField): return json.loads(obj) -class UserStorySerializer(WatchersValidator, VotedResourceSerializerMixin, WatchedResourceModelSerializer, serializers.ModelSerializer): +class UserStorySerializer(WatchersValidator, VoteResourceSerializerMixin, EditableWatchedResourceModelSerializer, serializers.ModelSerializer): tags = TagsField(default=[], required=False) external_reference = PgArrayField(required=False) points = RolePointsField(source="role_points", required=False) diff --git a/taiga/projects/votes/mixins/serializers.py b/taiga/projects/votes/mixins/serializers.py index a9c95900..c7f2eb5a 100644 --- a/taiga/projects/votes/mixins/serializers.py +++ b/taiga/projects/votes/mixins/serializers.py @@ -17,21 +17,27 @@ 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 +class FanResourceSerializerMixin(serializers.ModelSerializer): + is_fan = serializers.SerializerMethodField("get_is_fan") + total_fans = serializers.SerializerMethodField("get_total_fans") - def get_is_voted(self, obj): + def get_is_fan(self, obj): # The "is_voted" attribute is attached in the get_queryset of the viewset. - return getattr(obj, "is_voted", False) or False + return getattr(obj, "is_voter", False) or False + + def get_total_fans(self, obj): + # The "total_likes" attribute is attached in the get_queryset of the viewset. + return getattr(obj, "total_voters", 0) or 0 -class LikedResourceSerializerMixin(BaseVotedResourceSerializer): - likes = serializers.SerializerMethodField("get_votes_counter") - is_liked = serializers.SerializerMethodField("get_is_voted") +class VoteResourceSerializerMixin(serializers.ModelSerializer): + is_voter = serializers.SerializerMethodField("get_is_voter") + total_voters = serializers.SerializerMethodField("get_total_voters") + def get_is_voter(self, obj): + # The "is_voted" attribute is attached in the get_queryset of the viewset. + return getattr(obj, "is_voter", False) or False -class VotedResourceSerializerMixin(BaseVotedResourceSerializer): - votes = serializers.SerializerMethodField("get_votes_counter") - is_voted = serializers.SerializerMethodField("get_is_voted") + def get_total_voters(self, obj): + # The "total_likes" attribute is attached in the get_queryset of the viewset. + return getattr(obj, "total_voters", 0) or 0 diff --git a/taiga/projects/votes/mixins/viewsets.py b/taiga/projects/votes/mixins/viewsets.py index 3c2bfed5..8567b3d0 100644 --- a/taiga/projects/votes/mixins/viewsets.py +++ b/taiga/projects/votes/mixins/viewsets.py @@ -23,7 +23,7 @@ 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 +from taiga.projects.votes.utils import attach_total_voters_to_queryset, attach_is_voter_to_queryset class BaseVotedResource: @@ -33,10 +33,10 @@ class BaseVotedResource: # return self.attach_votes_attrs_to_queryset(qs) def attach_votes_attrs_to_queryset(self, queryset): - qs = attach_votes_count_to_queryset(queryset) + qs = attach_total_voters_to_queryset(queryset) if self.request.user.is_authenticated(): - qs = attach_is_vote_to_queryset(self.request.user, qs) + qs = attach_is_voter_to_queryset(self.request.user, qs) return qs diff --git a/taiga/projects/votes/services.py b/taiga/projects/votes/services.py index ddc1deae..8a6749dc 100644 --- a/taiga/projects/votes/services.py +++ b/taiga/projects/votes/services.py @@ -35,7 +35,6 @@ def add_vote(obj, user): obj_type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(obj) with atomic(): vote, created = Vote.objects.get_or_create(content_type=obj_type, object_id=obj.id, user=user) - if not created: return diff --git a/taiga/projects/votes/utils.py b/taiga/projects/votes/utils.py index f82b17b0..bc7d9d14 100644 --- a/taiga/projects/votes/utils.py +++ b/taiga/projects/votes/utils.py @@ -18,7 +18,7 @@ from django.apps import apps -def attach_votes_count_to_queryset(queryset, as_field="votes_count"): +def attach_total_voters_to_queryset(queryset, as_field="total_voters"): """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,8 @@ def attach_votes_count_to_queryset(queryset, as_field="votes_count"): """ model = queryset.model type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(model) - sql = """SELECT coalesce(SUM(votes_count), 0) FROM ( - SELECT coalesce(votes_votes.count, 0) votes_count + sql = """SELECT coalesce(SUM(total_voters), 0) FROM ( + SELECT coalesce(votes_votes.count, 0) total_voters FROM votes_votes WHERE votes_votes.content_type_id = {type_id} AND votes_votes.object_id = {tbl}.id @@ -46,7 +46,7 @@ def attach_votes_count_to_queryset(queryset, as_field="votes_count"): return qs -def attach_is_vote_to_queryset(user, queryset, as_field="is_voted"): +def attach_is_voter_to_queryset(user, queryset, as_field="is_voter"): """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 diff --git a/taiga/users/api.py b/taiga/users/api.py index e9696bfd..1d41d5e3 100644 --- a/taiga/users/api.py +++ b/taiga/users/api.py @@ -113,32 +113,62 @@ class UsersViewSet(ModelCrudViewSet): self.check_permissions(request, "stats", user) return response.Ok(services.get_stats_for_user(user, request.user)) + + def _serialize_liked_content(self, elem, **kwargs): + if elem.get("type") == "project": + serializer = serializers.FanSerializer + else: + serializer = serializers.VotedSerializer + + return serializer(elem, **kwargs) + + @detail_route(methods=["GET"]) - def favourites(self, request, *args, **kwargs): + def watched(self, request, *args, **kwargs): for_user = get_object_or_404(models.User, **kwargs) from_user = request.user - self.check_permissions(request, 'favourites', for_user) + self.check_permissions(request, 'watched', for_user) filters = { "type": request.GET.get("type", None), - "action": request.GET.get("action", None), "q": request.GET.get("q", None), } - self.object_list = services.get_favourites_list(for_user, from_user, **filters) + self.object_list = services.get_watched_list(for_user, from_user, **filters) page = self.paginate_queryset(self.object_list) + elements = page.object_list if page is not None else self.object_list extra_args = { - "many": True, "user_votes": services.get_voted_content_for_user(request.user), "user_watching": services.get_watched_content_for_user(request.user), } - if page is not None: - serializer = serializers.FavouriteSerializer(page.object_list, **extra_args) - else: - serializer = serializers.FavouriteSerializer(self.object_list, **extra_args) + response_data = [self._serialize_liked_content(elem, **extra_args).data for elem in elements] + return response.Ok(response_data) + + + @detail_route(methods=["GET"]) + def liked(self, request, *args, **kwargs): + for_user = get_object_or_404(models.User, **kwargs) + from_user = request.user + self.check_permissions(request, 'liked', for_user) + filters = { + "type": request.GET.get("type", None), + "q": request.GET.get("q", None), + } + + self.object_list = services.get_voted_list(for_user, from_user, **filters) + page = self.paginate_queryset(self.object_list) + elements = page.object_list if page is not None else self.object_list + + extra_args = { + "user_votes": services.get_voted_content_for_user(request.user), + "user_watching": services.get_watched_content_for_user(request.user), + } + + response_data = [self._serialize_liked_content(elem, **extra_args).data for elem in elements] + + return response.Ok(response_data) - return response.Ok(serializer.data) @list_route(methods=["POST"]) def password_recovery(self, request, pk=None): diff --git a/taiga/users/permissions.py b/taiga/users/permissions.py index dab7fe0f..d5600d88 100644 --- a/taiga/users/permissions.py +++ b/taiga/users/permissions.py @@ -46,7 +46,8 @@ class UserPermission(TaigaResourcePermission): remove_avatar_perms = IsAuthenticated() change_email_perms = AllowAny() contacts_perms = AllowAny() - favourites_perms = AllowAny() + liked_perms = AllowAny() + watched_perms = AllowAny() class RolesPermission(TaigaResourcePermission): diff --git a/taiga/users/serializers.py b/taiga/users/serializers.py index 2a9abade..f5e5c6d8 100644 --- a/taiga/users/serializers.py +++ b/taiga/users/serializers.py @@ -155,13 +155,12 @@ class ProjectRoleSerializer(serializers.ModelSerializer): ###################################################### -## Favourite +## Like ###################################################### -class FavouriteSerializer(serializers.Serializer): +class LikeSerializer(serializers.Serializer): type = serializers.CharField() - action = serializers.CharField() id = serializers.IntegerField() ref = serializers.IntegerField() slug = serializers.CharField() @@ -175,11 +174,8 @@ class FavouriteSerializer(serializers.Serializer): created_date = serializers.DateTimeField() is_private = serializers.SerializerMethodField("get_is_private") - is_voted = serializers.SerializerMethodField("get_is_voted") - is_watched = serializers.SerializerMethodField("get_is_watched") - + is_watcher = serializers.SerializerMethodField("get_is_watcher") total_watchers = serializers.IntegerField() - total_votes = serializers.IntegerField() project = serializers.SerializerMethodField("get_project") project_name = serializers.SerializerMethodField("get_project_name") @@ -196,7 +192,7 @@ class FavouriteSerializer(serializers.Serializer): self.user_watching = kwargs.pop("user_watching", {}) # Instantiate the superclass normally - super(FavouriteSerializer, self).__init__(*args, **kwargs) + super(LikeSerializer, self).__init__(*args, **kwargs) def _none_if_project(self, obj, property): type = obj.get("type", "") @@ -230,10 +226,7 @@ class FavouriteSerializer(serializers.Serializer): def get_project_is_private(self, obj): return self._none_if_project(obj, "project_is_private") - def get_is_voted(self, obj): - return obj["id"] in self.user_votes.get(obj["type"], []) - - def get_is_watched(self, obj): + def get_is_watcher(self, obj): return obj["id"] in self.user_watching.get(obj["type"], []) def get_photo(self, obj): @@ -248,3 +241,19 @@ class FavouriteSerializer(serializers.Serializer): def get_tags_color(self, obj): tags = obj.get("tags", []) return [{"name": tc[0], "color": tc[1]} for tc in obj.get("tags_colors", []) if tc[0] in tags] + + + +class FanSerializer(LikeSerializer): + is_fan = serializers.SerializerMethodField("get_is_fan") + total_fans = serializers.IntegerField(source="total_voters") + + def get_is_fan(self, obj): + return obj["id"] in self.user_votes.get(obj["type"], []) + +class VotedSerializer(LikeSerializer): + is_voter = serializers.SerializerMethodField("get_is_voter") + total_voters = serializers.IntegerField() + + def get_is_voter(self, obj): + return obj["id"] in self.user_votes.get(obj["type"], []) diff --git a/taiga/users/services.py b/taiga/users/services.py index e8df5362..d7f498d0 100644 --- a/taiga/users/services.py +++ b/taiga/users/services.py @@ -188,12 +188,12 @@ def get_watched_content_for_user(user): return user_watches -def _build_favourites_sql_for_projects(for_user): +def _build_watched_sql_for_projects(for_user): sql = """ - SELECT projects_project.id AS id, null AS ref, 'project' AS type, 'watch' AS action, + SELECT projects_project.id AS id, null AS ref, 'project' AS type, tags, notifications_notifypolicy.project_id AS object_id, projects_project.id AS project, slug AS slug, projects_project.name AS name, null AS subject, - notifications_notifypolicy.created_at as created_date, coalesce(watchers, 0) as total_watchers, coalesce(votes_votes.count, 0) total_votes, null AS assigned_to, + notifications_notifypolicy.created_at as created_date, coalesce(watchers, 0) as total_watchers, coalesce(votes_votes.count, 0) total_voters, null AS assigned_to, null as status, null as status_color FROM notifications_notifypolicy INNER JOIN projects_project @@ -207,11 +207,20 @@ def _build_favourites_sql_for_projects(for_user): LEFT JOIN votes_votes ON (projects_project.id = votes_votes.object_id AND {project_content_type_id} = votes_votes.content_type_id) WHERE notifications_notifypolicy.user_id = {for_user_id} - UNION - SELECT projects_project.id AS id, null AS ref, 'project' AS type, 'vote' AS action, + """ + sql = sql.format( + for_user_id=for_user.id, + ignore_notify_level=NotifyLevel.ignore, + project_content_type_id=ContentType.objects.get(app_label="projects", model="project").id) + return sql + + +def _build_voted_sql_for_projects(for_user): + sql = """ + SELECT projects_project.id AS id, null AS ref, 'project' AS type, tags, votes_vote.object_id AS object_id, projects_project.id AS project, slug AS slug, projects_project.name AS name, null AS subject, - votes_vote.created_date, coalesce(watchers, 0) as total_watchers, coalesce(votes_votes.count, 0) total_votes, null AS assigned_to, + votes_vote.created_date, coalesce(watchers, 0) as total_watchers, coalesce(votes_votes.count, 0) total_voters, null AS assigned_to, null as status, null as status_color FROM votes_vote INNER JOIN projects_project @@ -233,65 +242,43 @@ def _build_favourites_sql_for_projects(for_user): return sql - -def _build_favourites_sql_for_type(for_user, type, table_name, ref_column="ref", +def _build_sql_for_type(for_user, type, table_name, action_table, ref_column="ref", project_column="project_id", assigned_to_column="assigned_to_id", slug_column="slug", subject_column="subject"): sql = """ - SELECT {table_name}.id AS id, {ref_column} AS ref, '{type}' AS type, 'watch' AS action, - tags, notifications_watched.object_id AS object_id, {table_name}.{project_column} AS project, + SELECT {table_name}.id AS id, {ref_column} AS ref, '{type}' AS type, + tags, {action_table}.object_id AS object_id, {table_name}.{project_column} AS project, {slug_column} AS slug, null AS name, {subject_column} AS subject, - notifications_watched.created_date, coalesce(watchers, 0) as total_watchers, coalesce(votes_votes.count, 0) total_votes, {assigned_to_column} AS assigned_to, + {action_table}.created_date, coalesce(watchers, 0) as total_watchers, coalesce(votes_votes.count, 0) total_voters, {assigned_to_column} AS assigned_to, projects_{type}status.name as status, projects_{type}status.color as status_color - FROM notifications_watched + FROM {action_table} INNER JOIN django_content_type - ON (notifications_watched.content_type_id = django_content_type.id AND django_content_type.model = '{type}') + ON ({action_table}.content_type_id = django_content_type.id AND django_content_type.model = '{type}') INNER JOIN {table_name} - ON ({table_name}.id = notifications_watched.object_id) + ON ({table_name}.id = {action_table}.object_id) INNER JOIN projects_{type}status ON (projects_{type}status.id = {table_name}.status_id) LEFT JOIN (SELECT object_id, content_type_id, count(*) watchers FROM notifications_watched GROUP BY object_id, content_type_id) type_watchers ON {table_name}.id = type_watchers.object_id AND django_content_type.id = type_watchers.content_type_id LEFT JOIN votes_votes ON ({table_name}.id = votes_votes.object_id AND django_content_type.id = votes_votes.content_type_id) - WHERE notifications_watched.user_id = {for_user_id} - UNION - SELECT {table_name}.id AS id, {ref_column} AS ref, '{type}' AS type, 'vote' AS action, - tags, votes_vote.object_id AS object_id, {table_name}.{project_column} AS project, - {slug_column} AS slug, null AS name, {subject_column} AS subject, - votes_vote.created_date, coalesce(watchers, 0) as total_watchers, votes_votes.count total_votes, {assigned_to_column} AS assigned_to, - projects_{type}status.name as status, projects_{type}status.color as status_color - FROM votes_vote - INNER JOIN django_content_type - ON (votes_vote.content_type_id = django_content_type.id AND django_content_type.model = '{type}') - INNER JOIN {table_name} - ON ({table_name}.id = votes_vote.object_id) - INNER JOIN projects_{type}status - ON (projects_{type}status.id = {table_name}.status_id) - LEFT JOIN (SELECT object_id, content_type_id, count(*) watchers FROM notifications_watched GROUP BY object_id, content_type_id) type_watchers - ON {table_name}.id = type_watchers.object_id AND django_content_type.id = type_watchers.content_type_id - LEFT JOIN votes_votes - ON ({table_name}.id = votes_votes.object_id AND django_content_type.id = votes_votes.content_type_id) - WHERE votes_vote.user_id = {for_user_id} + WHERE {action_table}.user_id = {for_user_id} """ sql = sql.format(for_user_id=for_user.id, type=type, table_name=table_name, - ref_column = ref_column, project_column=project_column, - assigned_to_column=assigned_to_column, slug_column=slug_column, - subject_column=subject_column) + action_table=action_table, ref_column = ref_column, + project_column=project_column, assigned_to_column=assigned_to_column, + slug_column=slug_column, subject_column=subject_column) return sql -def get_favourites_list(for_user, from_user, type=None, action=None, q=None): +def _get_favourites_list(for_user, from_user, action_table, project_sql_builder, type=None, q=None): filters_sql = "" and_needed = False if type: filters_sql += " AND type = '{type}' ".format(type=type) - if action: - filters_sql += " AND action = '{action}' ".format(action=action) - if q: filters_sql += """ AND ( to_tsvector('english_nostop', coalesce(subject,'') || ' ' ||coalesce(entities.name,'') || ' ' ||coalesce(to_char(ref, '999'),'')) @@ to_tsquery('english_nostop', '{q}') @@ -361,10 +348,10 @@ def get_favourites_list(for_user, from_user, type=None, action=None, q=None): for_user_id=for_user.id, from_user_id=from_user_id, filters_sql=filters_sql, - userstories_sql=_build_favourites_sql_for_type(for_user, "userstory", "userstories_userstory", slug_column="null"), - tasks_sql=_build_favourites_sql_for_type(for_user, "task", "tasks_task", slug_column="null"), - issues_sql=_build_favourites_sql_for_type(for_user, "issue", "issues_issue", slug_column="null"), - projects_sql=_build_favourites_sql_for_projects(for_user)) + userstories_sql=_build_sql_for_type(for_user, "userstory", "userstories_userstory", action_table, slug_column="null"), + tasks_sql=_build_sql_for_type(for_user, "task", "tasks_task", action_table, slug_column="null"), + issues_sql=_build_sql_for_type(for_user, "issue", "issues_issue", action_table, slug_column="null"), + projects_sql=project_sql_builder(for_user)) cursor = connection.cursor() cursor.execute(sql) @@ -374,3 +361,11 @@ def get_favourites_list(for_user, from_user, type=None, action=None, q=None): dict(zip([col[0] for col in desc], row)) for row in cursor.fetchall() ] + + +def get_watched_list(for_user, from_user, type=None, q=None): + return _get_favourites_list(for_user, from_user, "notifications_watched", _build_watched_sql_for_projects, type=type, q=q) + + +def get_voted_list(for_user, from_user, type=None, q=None): + return _get_favourites_list(for_user, from_user, "votes_vote", _build_voted_sql_for_projects, type=type, q=q) diff --git a/taiga/webhooks/serializers.py b/taiga/webhooks/serializers.py index 47d0a145..3b0c90c8 100644 --- a/taiga/webhooks/serializers.py +++ b/taiga/webhooks/serializers.py @@ -25,7 +25,7 @@ from taiga.projects.issues import models as issue_models from taiga.projects.milestones import models as milestone_models from taiga.projects.wiki import models as wiki_models from taiga.projects.history import models as history_models -from taiga.projects.notifications.mixins import WatchedResourceModelSerializer +from taiga.projects.notifications.mixins import EditableWatchedResourceModelSerializer from .models import Webhook, WebhookLog @@ -104,7 +104,7 @@ class PointSerializer(serializers.Serializer): return obj.value -class UserStorySerializer(CustomAttributesValuesWebhookSerializerMixin, WatchedResourceModelSerializer, +class UserStorySerializer(CustomAttributesValuesWebhookSerializerMixin, EditableWatchedResourceModelSerializer, serializers.ModelSerializer): tags = TagsField(default=[], required=False) external_reference = PgArrayField(required=False) @@ -121,7 +121,7 @@ class UserStorySerializer(CustomAttributesValuesWebhookSerializerMixin, WatchedR return project.userstorycustomattributes.all() -class TaskSerializer(CustomAttributesValuesWebhookSerializerMixin, WatchedResourceModelSerializer, +class TaskSerializer(CustomAttributesValuesWebhookSerializerMixin, EditableWatchedResourceModelSerializer, serializers.ModelSerializer): tags = TagsField(default=[], required=False) owner = UserSerializer() @@ -135,7 +135,7 @@ class TaskSerializer(CustomAttributesValuesWebhookSerializerMixin, WatchedResour return project.taskcustomattributes.all() -class IssueSerializer(CustomAttributesValuesWebhookSerializerMixin, WatchedResourceModelSerializer, +class IssueSerializer(CustomAttributesValuesWebhookSerializerMixin, EditableWatchedResourceModelSerializer, serializers.ModelSerializer): tags = TagsField(default=[], required=False) owner = UserSerializer() diff --git a/tests/integration/resources_permissions/test_users_resources.py b/tests/integration/resources_permissions/test_users_resources.py index 761439d1..a78d270d 100644 --- a/tests/integration/resources_permissions/test_users_resources.py +++ b/tests/integration/resources_permissions/test_users_resources.py @@ -289,8 +289,20 @@ def test_user_action_change_email(client, data): assert results == [204, 204, 204] -def test_user_list_votes(client, data): - url = reverse('users-favourites', kwargs={"pk": data.registered_user.pk}) +def test_user_list_watched(client, data): + url = reverse('users-watched', kwargs={"pk": data.registered_user.pk}) + users = [ + None, + data.registered_user, + data.other_user, + data.superuser, + ] + results = helper_test_http_method(client, 'get', url, None, users) + assert results == [200, 200, 200, 200] + + +def test_user_list_liked(client, data): + url = reverse('users-liked', kwargs={"pk": data.registered_user.pk}) users = [ None, data.registered_user, diff --git a/tests/integration/test_star_projects.py b/tests/integration/test_star_projects.py deleted file mode 100644 index 2f2b87aa..00000000 --- a/tests/integration/test_star_projects.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (C) 2015 Andrey Antukh -# Copyright (C) 2015 Jesús Espino -# Copyright (C) 2015 David Barragán -# Copyright (C) 2015 Anler Hernández -# 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 . - -import pytest -from django.core.urlresolvers import reverse - -from .. import factories as f - -pytestmark = pytest.mark.django_db - - -def test_like_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-like", args=(project.id,)) - - client.login(user) - response = client.post(url) - - assert response.status_code == 200 - - -def test_unlike_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-unlike", 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_likes(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['likes'] == 5 - - -def test_get_project_is_liked(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_like = reverse("projects-like", args=(project.id,)) - url_unlike = reverse("projects-unlike", args=(project.id,)) - - client.login(user) - - response = client.get(url_detail) - assert response.status_code == 200 - assert response.data['likes'] == 0 - assert response.data['is_liked'] == False - - response = client.post(url_like) - assert response.status_code == 200 - - response = client.get(url_detail) - assert response.status_code == 200 - assert response.data['likes'] == 1 - assert response.data['is_liked'] == True - - response = client.post(url_unlike) - assert response.status_code == 200 - - response = client.get(url_detail) - assert response.status_code == 200 - assert response.data['likes'] == 0 - assert response.data['is_liked'] == False diff --git a/tests/integration/test_users.py b/tests/integration/test_users.py index 4ad11470..302a51a3 100644 --- a/tests/integration/test_users.py +++ b/tests/integration/test_users.py @@ -9,10 +9,10 @@ from .. import factories as f from taiga.base.utils import json from taiga.users import models -from taiga.users.serializers import FavouriteSerializer +from taiga.users.serializers import FanSerializer, VotedSerializer from taiga.auth.tokens import get_token_for_user from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS -from taiga.users.services import get_favourites_list +from taiga.users.services import get_watched_list, get_voted_list from easy_thumbnails.files import generate_all_aliases, get_thumbnailer @@ -348,7 +348,7 @@ def test_mail_permissions(client): assert "email" in response.data -def test_get_favourites_list(): +def test_get_watched_list(): fav_user = f.UserFactory() viewer_user = f.UserFactory() @@ -356,58 +356,117 @@ def test_get_favourites_list(): role = f.RoleFactory(project=project, permissions=["view_project", "view_us", "view_tasks", "view_issues"]) membership = f.MembershipFactory(project=project, role=role, user=fav_user) project.add_watcher(fav_user) + + user_story = f.UserStoryFactory(project=project, subject="Testing user story") + user_story.add_watcher(fav_user) + + task = f.TaskFactory(project=project, subject="Testing task") + task.add_watcher(fav_user) + + issue = f.IssueFactory(project=project, subject="Testing issue") + issue.add_watcher(fav_user) + + assert len(get_watched_list(fav_user, viewer_user)) == 4 + assert len(get_watched_list(fav_user, viewer_user, type="project")) == 1 + assert len(get_watched_list(fav_user, viewer_user, type="userstory")) == 1 + assert len(get_watched_list(fav_user, viewer_user, type="task")) == 1 + assert len(get_watched_list(fav_user, viewer_user, type="issue")) == 1 + assert len(get_watched_list(fav_user, viewer_user, type="unknown")) == 0 + + assert len(get_watched_list(fav_user, viewer_user, q="issue")) == 1 + assert len(get_watched_list(fav_user, viewer_user, q="unexisting text")) == 0 + + +def test_get_voted_list(): + fav_user = f.UserFactory() + viewer_user = f.UserFactory() + + project = f.ProjectFactory(is_private=False, name="Testing project") + role = f.RoleFactory(project=project, permissions=["view_project", "view_us", "view_tasks", "view_issues"]) + membership = f.MembershipFactory(project=project, role=role, user=fav_user) content_type = ContentType.objects.get_for_model(project) f.VoteFactory(content_type=content_type, object_id=project.id, user=fav_user) f.VotesFactory(content_type=content_type, object_id=project.id, count=1) user_story = f.UserStoryFactory(project=project, subject="Testing user story") - user_story.add_watcher(fav_user) content_type = ContentType.objects.get_for_model(user_story) f.VoteFactory(content_type=content_type, object_id=user_story.id, user=fav_user) f.VotesFactory(content_type=content_type, object_id=user_story.id, count=1) task = f.TaskFactory(project=project, subject="Testing task") - task.add_watcher(fav_user) content_type = ContentType.objects.get_for_model(task) f.VoteFactory(content_type=content_type, object_id=task.id, user=fav_user) f.VotesFactory(content_type=content_type, object_id=task.id, count=1) issue = f.IssueFactory(project=project, subject="Testing issue") - issue.add_watcher(fav_user) content_type = ContentType.objects.get_for_model(issue) f.VoteFactory(content_type=content_type, object_id=issue.id, user=fav_user) f.VotesFactory(content_type=content_type, object_id=issue.id, count=1) - assert len(get_favourites_list(fav_user, viewer_user)) == 8 - assert len(get_favourites_list(fav_user, viewer_user, type="project")) == 2 - assert len(get_favourites_list(fav_user, viewer_user, type="userstory")) == 2 - assert len(get_favourites_list(fav_user, viewer_user, type="task")) == 2 - assert len(get_favourites_list(fav_user, viewer_user, type="issue")) == 2 - assert len(get_favourites_list(fav_user, viewer_user, type="unknown")) == 0 + assert len(get_voted_list(fav_user, viewer_user)) == 4 + assert len(get_voted_list(fav_user, viewer_user, type="project")) == 1 + assert len(get_voted_list(fav_user, viewer_user, type="userstory")) == 1 + assert len(get_voted_list(fav_user, viewer_user, type="task")) == 1 + assert len(get_voted_list(fav_user, viewer_user, type="issue")) == 1 + assert len(get_voted_list(fav_user, viewer_user, type="unknown")) == 0 - assert len(get_favourites_list(fav_user, viewer_user, action="watch")) == 4 - assert len(get_favourites_list(fav_user, viewer_user, action="vote")) == 4 - - assert len(get_favourites_list(fav_user, viewer_user, q="issue")) == 2 - assert len(get_favourites_list(fav_user, viewer_user, q="unexisting text")) == 0 + assert len(get_voted_list(fav_user, viewer_user, q="issue")) == 1 + assert len(get_voted_list(fav_user, viewer_user, q="unexisting text")) == 0 -def test_get_favourites_list_valid_info_for_project(): +def test_get_watched_list_valid_info_for_project(): + fav_user = f.UserFactory() + viewer_user = f.UserFactory() + + project = f.ProjectFactory(is_private=False, name="Testing project", tags=['test', 'tag']) + role = f.RoleFactory(project=project, permissions=["view_project", "view_us", "view_tasks", "view_issues"]) + project.add_watcher(fav_user) + + raw_project_watch_info = get_watched_list(fav_user, viewer_user)[0] + project_watch_info = FanSerializer(raw_project_watch_info).data + + assert project_watch_info["type"] == "project" + assert project_watch_info["id"] == project.id + assert project_watch_info["ref"] == None + assert project_watch_info["slug"] == project.slug + assert project_watch_info["name"] == project.name + assert project_watch_info["subject"] == None + assert project_watch_info["description"] == project.description + assert project_watch_info["assigned_to"] == None + assert project_watch_info["status"] == None + assert project_watch_info["status_color"] == None + + tags_colors = {tc["name"]:tc["color"] for tc in project_watch_info["tags_colors"]} + assert "test" in tags_colors + assert "tag" in tags_colors + + assert project_watch_info["is_private"] == project.is_private + assert project_watch_info["is_fan"] == False + assert project_watch_info["is_watcher"] == False + assert project_watch_info["total_watchers"] == 1 + assert project_watch_info["total_fans"] == 0 + assert project_watch_info["project"] == None + assert project_watch_info["project_name"] == None + assert project_watch_info["project_slug"] == None + assert project_watch_info["project_is_private"] == None + assert project_watch_info["assigned_to_username"] == None + assert project_watch_info["assigned_to_full_name"] == None + assert project_watch_info["assigned_to_photo"] == None + + +def test_get_voted_list_valid_info_for_project(): fav_user = f.UserFactory() viewer_user = f.UserFactory() - watcher_user = f.UserFactory() project = f.ProjectFactory(is_private=False, name="Testing project", tags=['test', 'tag']) - project.add_watcher(watcher_user) content_type = ContentType.objects.get_for_model(project) vote = f.VoteFactory(content_type=content_type, object_id=project.id, user=fav_user) f.VotesFactory(content_type=content_type, object_id=project.id, count=1) - raw_project_vote_info = get_favourites_list(fav_user, viewer_user)[0] - project_vote_info = FavouriteSerializer(raw_project_vote_info).data + raw_project_vote_info = get_voted_list(fav_user, viewer_user)[0] + project_vote_info = FanSerializer(raw_project_vote_info).data assert project_vote_info["type"] == "project" - assert project_vote_info["action"] == "vote" assert project_vote_info["id"] == project.id assert project_vote_info["ref"] == None assert project_vote_info["slug"] == project.slug @@ -423,10 +482,14 @@ def test_get_favourites_list_valid_info_for_project(): assert "tag" in tags_colors assert project_vote_info["is_private"] == project.is_private - assert project_vote_info["is_voted"] == False - assert project_vote_info["is_watched"] == False - assert project_vote_info["total_watchers"] == 1 - assert project_vote_info["total_votes"] == 1 + + import pprint + pprint.pprint(project_vote_info) + + assert project_vote_info["is_fan"] == False + assert project_vote_info["is_watcher"] == False + assert project_vote_info["total_watchers"] == 0 + assert project_vote_info["total_fans"] == 1 assert project_vote_info["project"] == None assert project_vote_info["project_name"] == None assert project_vote_info["project_slug"] == None @@ -436,10 +499,61 @@ def test_get_favourites_list_valid_info_for_project(): assert project_vote_info["assigned_to_photo"] == None -def test_get_favourites_list_valid_info_for_not_project_types(): +def test_get_watched_list_valid_info_for_not_project_types(): + fav_user = f.UserFactory() + viewer_user = f.UserFactory() + assigned_to_user = f.UserFactory() + + project = f.ProjectFactory(is_private=False, name="Testing project") + + factories = { + "userstory": f.UserStoryFactory, + "task": f.TaskFactory, + "issue": f.IssueFactory + } + + for object_type in factories: + instance = factories[object_type](project=project, + subject="Testing", + tags=["test1", "test2"], + assigned_to=assigned_to_user) + + instance.add_watcher(fav_user) + raw_instance_watch_info = get_watched_list(fav_user, viewer_user, type=object_type)[0] + instance_watch_info = VotedSerializer(raw_instance_watch_info).data + + assert instance_watch_info["type"] == object_type + assert instance_watch_info["id"] == instance.id + assert instance_watch_info["ref"] == instance.ref + assert instance_watch_info["slug"] == None + assert instance_watch_info["name"] == None + assert instance_watch_info["subject"] == instance.subject + assert instance_watch_info["description"] == None + assert instance_watch_info["assigned_to"] == instance.assigned_to.id + assert instance_watch_info["status"] == instance.status.name + assert instance_watch_info["status_color"] == instance.status.color + + tags_colors = {tc["name"]:tc["color"] for tc in instance_watch_info["tags_colors"]} + assert "test1" in tags_colors + assert "test2" in tags_colors + + assert instance_watch_info["is_private"] == None + assert instance_watch_info["is_voter"] == False + assert instance_watch_info["is_watcher"] == False + assert instance_watch_info["total_watchers"] == 1 + assert instance_watch_info["total_voters"] == 0 + assert instance_watch_info["project"] == instance.project.id + assert instance_watch_info["project_name"] == instance.project.name + assert instance_watch_info["project_slug"] == instance.project.slug + assert instance_watch_info["project_is_private"] == instance.project.is_private + assert instance_watch_info["assigned_to_username"] == instance.assigned_to.username + assert instance_watch_info["assigned_to_full_name"] == instance.assigned_to.full_name + assert instance_watch_info["assigned_to_photo"] != "" + + +def test_get_voted_list_valid_info_for_not_project_types(): fav_user = f.UserFactory() viewer_user = f.UserFactory() - watcher_user = f.UserFactory() assigned_to_user = f.UserFactory() project = f.ProjectFactory(is_private=False, name="Testing project") @@ -456,16 +570,14 @@ def test_get_favourites_list_valid_info_for_not_project_types(): tags=["test1", "test2"], assigned_to=assigned_to_user) - instance.add_watcher(watcher_user) content_type = ContentType.objects.get_for_model(instance) vote = f.VoteFactory(content_type=content_type, object_id=instance.id, user=fav_user) f.VotesFactory(content_type=content_type, object_id=instance.id, count=3) - raw_instance_vote_info = get_favourites_list(fav_user, viewer_user, type=object_type)[0] - instance_vote_info = FavouriteSerializer(raw_instance_vote_info).data + raw_instance_vote_info = get_voted_list(fav_user, viewer_user, type=object_type)[0] + instance_vote_info = VotedSerializer(raw_instance_vote_info).data assert instance_vote_info["type"] == object_type - assert instance_vote_info["action"] == "vote" assert instance_vote_info["id"] == instance.id assert instance_vote_info["ref"] == instance.ref assert instance_vote_info["slug"] == None @@ -481,10 +593,10 @@ def test_get_favourites_list_valid_info_for_not_project_types(): assert "test2" in tags_colors assert instance_vote_info["is_private"] == None - assert instance_vote_info["is_voted"] == False - assert instance_vote_info["is_watched"] == False - assert instance_vote_info["total_watchers"] == 1 - assert instance_vote_info["total_votes"] == 3 + assert instance_vote_info["is_voter"] == False + assert instance_vote_info["is_watcher"] == False + assert instance_vote_info["total_watchers"] == 0 + assert instance_vote_info["total_voters"] == 3 assert instance_vote_info["project"] == instance.project.id assert instance_vote_info["project_name"] == instance.project.name assert instance_vote_info["project_slug"] == instance.project.slug @@ -494,7 +606,41 @@ def test_get_favourites_list_valid_info_for_not_project_types(): assert instance_vote_info["assigned_to_photo"] != "" -def test_get_favourites_list_permissions(): +def test_get_watched_list_permissions(): + fav_user = f.UserFactory() + viewer_unpriviliged_user = f.UserFactory() + viewer_priviliged_user = f.UserFactory() + + project = f.ProjectFactory(is_private=True, name="Testing project") + project.add_watcher(fav_user) + role = f.RoleFactory(project=project, permissions=["view_project", "view_us", "view_tasks", "view_issues"]) + membership = f.MembershipFactory(project=project, role=role, user=viewer_priviliged_user) + + user_story = f.UserStoryFactory(project=project, subject="Testing user story") + user_story.add_watcher(fav_user) + + task = f.TaskFactory(project=project, subject="Testing task") + task.add_watcher(fav_user) + + issue = f.IssueFactory(project=project, subject="Testing issue") + issue.add_watcher(fav_user) + + #If the project is private a viewer user without any permission shouldn' see + # any vote + assert len(get_watched_list(fav_user, viewer_unpriviliged_user)) == 0 + + #If the project is private but the viewer user has permissions the votes should + # be accesible + assert len(get_watched_list(fav_user, viewer_priviliged_user)) == 4 + + #If the project is private but has the required anon permissions the votes should + # be accesible by any user too + project.anon_permissions = ["view_project", "view_us", "view_tasks", "view_issues"] + project.save() + assert len(get_watched_list(fav_user, viewer_unpriviliged_user)) == 4 + + +def test_get_voted_list_permissions(): fav_user = f.UserFactory() viewer_unpriviliged_user = f.UserFactory() viewer_priviliged_user = f.UserFactory() @@ -523,14 +669,14 @@ def test_get_favourites_list_permissions(): #If the project is private a viewer user without any permission shouldn' see # any vote - assert len(get_favourites_list(fav_user, viewer_unpriviliged_user)) == 0 + assert len(get_voted_list(fav_user, viewer_unpriviliged_user)) == 0 #If the project is private but the viewer user has permissions the votes should # be accesible - assert len(get_favourites_list(fav_user, viewer_priviliged_user)) == 4 + assert len(get_voted_list(fav_user, viewer_priviliged_user)) == 4 #If the project is private but has the required anon permissions the votes should # be accesible by any user too project.anon_permissions = ["view_project", "view_us", "view_tasks", "view_issues"] project.save() - assert len(get_favourites_list(fav_user, viewer_unpriviliged_user)) == 4 + assert len(get_voted_list(fav_user, viewer_unpriviliged_user)) == 4 diff --git a/tests/integration/test_vote_issues.py b/tests/integration/test_vote_issues.py index 8faca67b..5076b227 100644 --- a/tests/integration/test_vote_issues.py +++ b/tests/integration/test_vote_issues.py @@ -85,7 +85,7 @@ def test_get_issue_votes(client): response = client.get(url) assert response.status_code == 200 - assert response.data['votes'] == 5 + assert response.data['total_voters'] == 5 def test_get_issue_is_voted(client): @@ -101,21 +101,21 @@ def test_get_issue_is_voted(client): response = client.get(url_detail) assert response.status_code == 200 - assert response.data['votes'] == 0 - assert response.data['is_voted'] == False + assert response.data['total_voters'] == 0 + assert response.data['is_voter'] == 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 + assert response.data['total_voters'] == 1 + assert response.data['is_voter'] == 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 + assert response.data['total_voters'] == 0 + assert response.data['is_voter'] == False diff --git a/tests/integration/test_vote_tasks.py b/tests/integration/test_vote_tasks.py index a5cdea56..7d71eff4 100644 --- a/tests/integration/test_vote_tasks.py +++ b/tests/integration/test_vote_tasks.py @@ -87,7 +87,7 @@ def test_get_task_votes(client): response = client.get(url) assert response.status_code == 200 - assert response.data['votes'] == 5 + assert response.data['total_voters'] == 5 def test_get_task_is_voted(client): @@ -103,21 +103,21 @@ def test_get_task_is_voted(client): response = client.get(url_detail) assert response.status_code == 200 - assert response.data['votes'] == 0 - assert response.data['is_voted'] == False + assert response.data['total_voters'] == 0 + assert response.data['is_voter'] == 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 + assert response.data['total_voters'] == 1 + assert response.data['is_voter'] == 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 + assert response.data['total_voters'] == 0 + assert response.data['is_voter'] == False diff --git a/tests/integration/test_vote_userstories.py b/tests/integration/test_vote_userstories.py index ab863df3..492f8121 100644 --- a/tests/integration/test_vote_userstories.py +++ b/tests/integration/test_vote_userstories.py @@ -86,7 +86,7 @@ def test_get_user_story_votes(client): response = client.get(url) assert response.status_code == 200 - assert response.data['votes'] == 5 + assert response.data['total_voters'] == 5 def test_get_user_story_is_voted(client): @@ -102,21 +102,21 @@ def test_get_user_story_is_voted(client): response = client.get(url_detail) assert response.status_code == 200 - assert response.data['votes'] == 0 - assert response.data['is_voted'] == False + assert response.data['total_voters'] == 0 + assert response.data['is_voter'] == 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 + assert response.data['total_voters'] == 1 + assert response.data['is_voter'] == 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 + assert response.data['total_voters'] == 0 + assert response.data['is_voter'] == False diff --git a/tests/integration/test_watch_issues.py b/tests/integration/test_watch_issues.py index 09ba4f7b..91c64ec8 100644 --- a/tests/integration/test_watch_issues.py +++ b/tests/integration/test_watch_issues.py @@ -89,9 +89,10 @@ def test_get_issue_watchers(client): assert response.status_code == 200 assert response.data['watchers'] == [user.id] + assert response.data['total_watchers'] == 1 -def test_get_issue_is_watched(client): +def test_get_issue_is_watcher(client): user = f.UserFactory.create() issue = f.IssueFactory(owner=user) f.MembershipFactory.create(project=issue.project, user=user, is_owner=True) @@ -104,7 +105,7 @@ def test_get_issue_is_watched(client): response = client.get(url_detail) assert response.status_code == 200 assert response.data['watchers'] == [] - assert response.data['is_watched'] == False + assert response.data['is_watcher'] == False response = client.post(url_watch) assert response.status_code == 200 @@ -112,7 +113,7 @@ def test_get_issue_is_watched(client): response = client.get(url_detail) assert response.status_code == 200 assert response.data['watchers'] == [user.id] - assert response.data['is_watched'] == True + assert response.data['is_watcher'] == True response = client.post(url_unwatch) assert response.status_code == 200 @@ -120,4 +121,4 @@ def test_get_issue_is_watched(client): response = client.get(url_detail) assert response.status_code == 200 assert response.data['watchers'] == [] - assert response.data['is_watched'] == False + assert response.data['is_watcher'] == False diff --git a/tests/integration/test_watch_milestones.py b/tests/integration/test_watch_milestones.py index 72fea24d..f744086c 100644 --- a/tests/integration/test_watch_milestones.py +++ b/tests/integration/test_watch_milestones.py @@ -88,10 +88,10 @@ def test_get_milestone_watchers(client): response = client.get(url) assert response.status_code == 200 - assert response.data['watchers'] == [user.id] + assert response.data['total_watchers'] == 1 -def test_get_milestone_is_watched(client): +def test_get_milestone_is_watcher(client): user = f.UserFactory.create() milestone = f.MilestoneFactory(owner=user) f.MembershipFactory.create(project=milestone.project, user=user, is_owner=True) @@ -103,21 +103,21 @@ def test_get_milestone_is_watched(client): response = client.get(url_detail) assert response.status_code == 200 - assert response.data['watchers'] == [] - assert response.data['is_watched'] == False + assert response.data['total_watchers'] == 0 + assert response.data['is_watcher'] == False response = client.post(url_watch) assert response.status_code == 200 response = client.get(url_detail) assert response.status_code == 200 - assert response.data['watchers'] == [user.id] - assert response.data['is_watched'] == True + assert response.data['total_watchers'] == 1 + assert response.data['is_watcher'] == True response = client.post(url_unwatch) assert response.status_code == 200 response = client.get(url_detail) assert response.status_code == 200 - assert response.data['watchers'] == [] - assert response.data['is_watched'] == False + assert response.data['total_watchers'] == 0 + assert response.data['is_watcher'] == False diff --git a/tests/integration/test_watch_projects.py b/tests/integration/test_watch_projects.py index bbf58a0c..8b1bf4da 100644 --- a/tests/integration/test_watch_projects.py +++ b/tests/integration/test_watch_projects.py @@ -121,10 +121,10 @@ def test_get_project_watchers(client): response = client.get(url) assert response.status_code == 200 - assert response.data['watchers'] == [user.id] + assert response.data['total_watchers'] == 1 -def test_get_project_is_watched(client): +def test_get_project_is_watcher(client): user = f.UserFactory.create() project = f.ProjectFactory.create(is_private=False, anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)), @@ -139,21 +139,22 @@ def test_get_project_is_watched(client): response = client.get(url_detail) assert response.status_code == 200 - assert response.data['watchers'] == [] - assert response.data['is_watched'] == False + assert response.data['total_watchers'] == 0 + + assert response.data['is_watcher'] == False response = client.post(url_watch) assert response.status_code == 200 response = client.get(url_detail) assert response.status_code == 200 - assert response.data['watchers'] == [user.id] - assert response.data['is_watched'] == True + assert response.data['total_watchers'] == 1 + assert response.data['is_watcher'] == True response = client.post(url_unwatch) assert response.status_code == 200 response = client.get(url_detail) assert response.status_code == 200 - assert response.data['watchers'] == [] - assert response.data['is_watched'] == False + assert response.data['total_watchers'] == 0 + assert response.data['is_watcher'] == False diff --git a/tests/integration/test_watch_tasks.py b/tests/integration/test_watch_tasks.py index 7444a948..449cba88 100644 --- a/tests/integration/test_watch_tasks.py +++ b/tests/integration/test_watch_tasks.py @@ -89,9 +89,10 @@ def test_get_task_watchers(client): assert response.status_code == 200 assert response.data['watchers'] == [user.id] + assert response.data['total_watchers'] == 1 -def test_get_task_is_watched(client): +def test_get_task_is_watcher(client): user = f.UserFactory.create() task = f.TaskFactory(owner=user) f.MembershipFactory.create(project=task.project, user=user, is_owner=True) @@ -104,7 +105,7 @@ def test_get_task_is_watched(client): response = client.get(url_detail) assert response.status_code == 200 assert response.data['watchers'] == [] - assert response.data['is_watched'] == False + assert response.data['is_watcher'] == False response = client.post(url_watch) assert response.status_code == 200 @@ -112,7 +113,7 @@ def test_get_task_is_watched(client): response = client.get(url_detail) assert response.status_code == 200 assert response.data['watchers'] == [user.id] - assert response.data['is_watched'] == True + assert response.data['is_watcher'] == True response = client.post(url_unwatch) assert response.status_code == 200 @@ -120,4 +121,4 @@ def test_get_task_is_watched(client): response = client.get(url_detail) assert response.status_code == 200 assert response.data['watchers'] == [] - assert response.data['is_watched'] == False + assert response.data['is_watcher'] == False diff --git a/tests/integration/test_watch_userstories.py b/tests/integration/test_watch_userstories.py index cad86151..86ae3ef0 100644 --- a/tests/integration/test_watch_userstories.py +++ b/tests/integration/test_watch_userstories.py @@ -89,9 +89,10 @@ def test_get_user_story_watchers(client): assert response.status_code == 200 assert response.data['watchers'] == [user.id] + assert response.data['total_watchers'] == 1 -def test_get_user_story_is_watched(client): +def test_get_user_story_is_watcher(client): user = f.UserFactory.create() user_story = f.UserStoryFactory(owner=user) f.MembershipFactory.create(project=user_story.project, user=user, is_owner=True) @@ -104,7 +105,7 @@ def test_get_user_story_is_watched(client): response = client.get(url_detail) assert response.status_code == 200 assert response.data['watchers'] == [] - assert response.data['is_watched'] == False + assert response.data['is_watcher'] == False response = client.post(url_watch) assert response.status_code == 200 @@ -112,7 +113,7 @@ def test_get_user_story_is_watched(client): response = client.get(url_detail) assert response.status_code == 200 assert response.data['watchers'] == [user.id] - assert response.data['is_watched'] == True + assert response.data['is_watcher'] == True response = client.post(url_unwatch) assert response.status_code == 200 @@ -120,4 +121,4 @@ def test_get_user_story_is_watched(client): response = client.get(url_detail) assert response.status_code == 200 assert response.data['watchers'] == [] - assert response.data['is_watched'] == False + assert response.data['is_watcher'] == False diff --git a/tests/integration/test_watch_wikipages.py b/tests/integration/test_watch_wikipages.py index c4f96bb6..a26fe2a4 100644 --- a/tests/integration/test_watch_wikipages.py +++ b/tests/integration/test_watch_wikipages.py @@ -88,10 +88,10 @@ def test_get_wikipage_watchers(client): response = client.get(url) assert response.status_code == 200 - assert response.data['watchers'] == [user.id] + assert response.data['total_watchers'] == 1 -def test_get_wikipage_is_watched(client): +def test_get_wikipage_is_watcher(client): user = f.UserFactory.create() wikipage = f.WikiPageFactory(owner=user) f.MembershipFactory.create(project=wikipage.project, user=user, is_owner=True) @@ -103,21 +103,21 @@ def test_get_wikipage_is_watched(client): response = client.get(url_detail) assert response.status_code == 200 - assert response.data['watchers'] == [] - assert response.data['is_watched'] == False + assert response.data['total_watchers'] == 0 + assert response.data['is_watcher'] == False response = client.post(url_watch) assert response.status_code == 200 response = client.get(url_detail) assert response.status_code == 200 - assert response.data['watchers'] == [user.id] - assert response.data['is_watched'] == True + assert response.data['total_watchers'] == 1 + assert response.data['is_watcher'] == True response = client.post(url_unwatch) assert response.status_code == 200 response = client.get(url_detail) assert response.status_code == 200 - assert response.data['watchers'] == [] - assert response.data['is_watched'] == False + assert response.data['total_watchers'] == 0 + assert response.data['is_watcher'] == False