diff --git a/taiga/users/api.py b/taiga/users/api.py index 487229d4..f8ed27ff 100644 --- a/taiga/users/api.py +++ b/taiga/users/api.py @@ -111,7 +111,7 @@ class UsersViewSet(ModelCrudViewSet): def stats(self, request, *args, **kwargs): user = get_object_or_404(models.User, **kwargs) self.check_permissions(request, "stats", user) - return response.Ok(services.get_stats_for_user(user)) + return response.Ok(services.get_stats_for_user(user, request.user)) @list_route(methods=["POST"]) def password_recovery(self, request, pk=None): diff --git a/taiga/users/filters.py b/taiga/users/filters.py index 5e7ceb92..df7f04ae 100644 --- a/taiga/users/filters.py +++ b/taiga/users/filters.py @@ -15,30 +15,14 @@ # along with this program. If not, see . from django.apps import apps -from django.db.models import Q from taiga.base.filters import PermissionBasedFilterBackend +from . import services class ContactsFilterBackend(PermissionBasedFilterBackend): - permission = "view_project" - def filter_queryset(self, user, request, queryset, view): qs = queryset.filter(is_active=True) - Membership = apps.get_model('projects', 'Membership') - memberships_qs = Membership.objects.filter(user=user) - - # Authenticated - if request.user.is_authenticated(): - # if super user we don't need to filter anything - if not request.user.is_superuser: - memberships_qs = memberships_qs.filter(Q(role__permissions__contains=[self.permission]) | - Q(is_owner=True)) - - # Anonymous - else: - memberships_qs = memberships_qs.filter(project__anon_permissions__contains=[self.permission]) - - projects_list = [membership.project_id for membership in memberships_qs] - qs = qs.filter(memberships__project_id__in=projects_list) + project_ids = services.get_visible_project_ids(user, request.user) + qs = qs.filter(memberships__project_id__in=project_ids) qs = qs.exclude(id=user.id) return qs.distinct() diff --git a/taiga/users/services.py b/taiga/users/services.py index d1f765bf..caf27226 100644 --- a/taiga/users/services.py +++ b/taiga/users/services.py @@ -89,21 +89,51 @@ def get_big_photo_or_gravatar_url(user): else: return get_gravatar_url(user.email, size=settings.DEFAULT_BIG_AVATAR_SIZE) -def get_stats_for_user(user): - """Get the user stats""" - project_ids = user.memberships.values_list("project__id", flat=True).distinct() - total_num_projects = project_ids.count() - roles = [_(r) for r in user.memberships.values_list("role__name", flat=True)] +def get_visible_project_ids(from_user, by_user): + """Calculate the project_ids from one user visible by another""" + required_permissions = ["view_project"] + #Or condition for membership filtering, the basic one is the access to projects allowing anonymous visualization + member_perm_conditions = Q(project__anon_permissions__contains=required_permissions) + + # Authenticated + if by_user.is_authenticated(): + #Calculating the projects wich from_user user is member + by_user_project_ids = by_user.memberships.values_list("project__id", flat=True) + #Adding to the condition two OR situations: + #- The from user has a role that allows access to the project + #- The to user is the owner + member_perm_conditions |= \ + Q(project__id__in=by_user_project_ids, role__permissions__contains=required_permissions) |\ + Q(project__id__in=by_user_project_ids, is_owner=True) + + Membership = apps.get_model('projects', 'Membership') + #Calculating the user memberships adding the permission filter for the by user + memberships_qs = Membership.objects.filter(member_perm_conditions, user=from_user) + project_ids = memberships_qs.values_list("project__id", flat=True) + return project_ids + + +def get_stats_for_user(from_user, by_user): + """Get the user stats""" + project_ids = get_visible_project_ids(from_user, by_user) + + total_num_projects = len(project_ids) + + roles = [_(r) for r in from_user.memberships.filter(project__id__in=project_ids).values_list("role__name", flat=True)] roles = list(set(roles)) + User = apps.get_model('users', 'User') total_num_contacts = User.objects.filter(memberships__project__id__in=project_ids)\ - .exclude(id=user.id)\ + .exclude(id=from_user.id)\ .distinct()\ .count() UserStory = apps.get_model('userstories', 'UserStory') - total_num_closed_userstories = UserStory.objects.filter(is_closed=True, assigned_to=user).count() + total_num_closed_userstories = UserStory.objects.filter( + is_closed=True, + project__id__in=project_ids, + assigned_to=from_user).count() project_stats = { 'total_num_projects': total_num_projects, diff --git a/tests/unit/test_timeline.py b/tests/unit/test_timeline.py index 2a4c52ed..c580404e 100644 --- a/tests/unit/test_timeline.py +++ b/tests/unit/test_timeline.py @@ -57,13 +57,6 @@ def test_add_to_objects_timeline(): service.push_to_timeline(None, project, "test") -def test_modify_created_timeline_entry(): - timeline = Timeline() - timeline.pk = 3 - with pytest.raises(ValidationError): - timeline.save() - - def test_get_impl_key_from_model(): assert service._get_impl_key_from_model(Timeline, "test") == "timeline.timeline.test" with pytest.raises(Exception):