Merge pull request #312 from taigaio/user-contacts-API

User contacts API
remotes/origin/enhancement/email-actions
David Barragán Merino 2015-04-28 14:19:02 +02:00
commit f0d53c7bd7
5 changed files with 153 additions and 3 deletions

View File

@ -44,6 +44,7 @@ from djmail.template_mail import InlineCSSTemplateMail
from . import models from . import models
from . import serializers from . import serializers
from . import permissions from . import permissions
from . import filters as user_filters
from .signals import user_cancel_account as user_cancel_account_signal from .signals import user_cancel_account as user_cancel_account_signal
@ -51,7 +52,7 @@ class UsersViewSet(ModelCrudViewSet):
permission_classes = (permissions.UserPermission,) permission_classes = (permissions.UserPermission,)
admin_serializer_class = serializers.UserAdminSerializer admin_serializer_class = serializers.UserAdminSerializer
serializer_class = serializers.UserSerializer serializer_class = serializers.UserSerializer
queryset = models.User.objects.all() queryset = models.User.objects.all().prefetch_related("memberships")
filter_backends = (MembersFilterBackend,) filter_backends = (MembersFilterBackend,)
def get_serializer_class(self): def get_serializer_class(self):
@ -77,6 +78,23 @@ class UsersViewSet(ModelCrudViewSet):
return response.Ok(serializer.data) return response.Ok(serializer.data)
@detail_route(methods=["GET"])
def contacts(self, request, *args, **kwargs):
user = self.get_object()
self.check_permissions(request, 'contacts', user)
self.object_list = user_filters.ContactsFilterBackend().filter_queryset(request,
self.get_queryset(),
self)
page = self.paginate_queryset(self.object_list)
if page is not None:
serializer = self.serializer_class(page.object_list, many=True)
else:
serializer = self.serializer_class(self.object_list, many=True)
return response.Ok(serializer.data)
@list_route(methods=["POST"]) @list_route(methods=["POST"])
def password_recovery(self, request, pk=None): def password_recovery(self, request, pk=None):
username_or_email = request.DATA.get('username', None) username_or_email = request.DATA.get('username', None)

46
taiga/users/filters.py Normal file
View File

@ -0,0 +1,46 @@
# 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>
# 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.apps import apps
from django.db.models import Q
from taiga.base.filters import PermissionBasedFilterBackend
class ContactsFilterBackend(PermissionBasedFilterBackend):
permission = "view_project"
def filter_queryset(self, request, queryset, view):
qs = queryset.filter(is_active=True)
# Authenticated
if request.user.is_authenticated():
# if super user we don't need to filter anything
if not request.user.is_superuser:
Membership = apps.get_model('projects', 'Membership')
memberships_qs = Membership.objects.filter(user=request.user)
memberships_qs = memberships_qs.filter(Q(role__permissions__contains=[self.permission]) |
Q(is_owner=True))
projects_list = [membership.project_id for membership in memberships_qs]
qs = qs.filter(memberships__project_id__in=projects_list)
qs = qs.exclude(id=request.user.id)
# Anonymous
else:
qs = qs.filter(memberships__project__anon_permissions__contains=[self.permission])
return qs.distinct()

View File

@ -43,6 +43,7 @@ class UserPermission(TaigaResourcePermission):
remove_avatar_perms = IsAuthenticated() remove_avatar_perms = IsAuthenticated()
starred_perms = AllowAny() starred_perms = AllowAny()
change_email_perms = IsTheSameUser() change_email_perms = IsTheSameUser()
contacts_perms = AllowAny()
class RolesPermission(TaigaResourcePermission): class RolesPermission(TaigaResourcePermission):

View File

@ -20,7 +20,7 @@ from django.utils.translation import ugettext_lazy as _
from taiga.base.api import serializers from taiga.base.api import serializers
from taiga.base.fields import PgArrayField from taiga.base.fields import PgArrayField
from taiga.projects.models import Project
from .models import User, Role from .models import User, Role
from .services import get_photo_or_gravatar_url, get_big_photo_or_gravatar_url from .services import get_photo_or_gravatar_url, get_big_photo_or_gravatar_url
@ -31,10 +31,18 @@ import re
## User ## User
###################################################### ######################################################
class ContactProjectDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ("id", "slug", "name")
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
full_name_display = serializers.SerializerMethodField("get_full_name_display") full_name_display = serializers.SerializerMethodField("get_full_name_display")
photo = serializers.SerializerMethodField("get_photo") photo = serializers.SerializerMethodField("get_photo")
big_photo = serializers.SerializerMethodField("get_big_photo") big_photo = serializers.SerializerMethodField("get_big_photo")
roles = serializers.SerializerMethodField("get_roles")
projects_with_me = serializers.SerializerMethodField("get_projects_with_me")
class Meta: class Meta:
model = User model = User
@ -42,7 +50,7 @@ class UserSerializer(serializers.ModelSerializer):
# with this info (including there the email) # with this info (including there the email)
fields = ("id", "username", "full_name", "full_name_display", fields = ("id", "username", "full_name", "full_name_display",
"color", "bio", "lang", "timezone", "is_active", "color", "bio", "lang", "timezone", "is_active",
"photo", "big_photo") "photo", "big_photo", "roles", "projects_with_me")
read_only_fields = ("id",) read_only_fields = ("id",)
def validate_username(self, attrs, source): def validate_username(self, attrs, source):
@ -72,6 +80,22 @@ class UserSerializer(serializers.ModelSerializer):
def get_big_photo(self, user): def get_big_photo(self, user):
return get_big_photo_or_gravatar_url(user) return get_big_photo_or_gravatar_url(user)
def get_roles(self, user):
return user.memberships. order_by("role__name").values_list("role__name", flat=True).distinct()
def get_projects_with_me(self, user):
request = self.context.get("request", None)
requesting_user = request and request.user or None
if not requesting_user or not requesting_user.is_authenticated():
return []
else:
project_ids = requesting_user.memberships.values_list("project__id", flat=True)
memberships = requesting_user.memberships.filter(project__id__in=project_ids)
project_ids = memberships.values_list("project__id", flat=True)
projects = Project.objects.filter(id__in=project_ids)
return ContactProjectDetailSerializer(projects, many=True).data
class UserAdminSerializer(UserSerializer): class UserAdminSerializer(UserSerializer):
class Meta: class Meta:

View File

@ -8,6 +8,7 @@ from .. import factories as f
from taiga.base.utils import json from taiga.base.utils import json
from taiga.users import models from taiga.users import models
from taiga.auth.tokens import get_token_for_user from taiga.auth.tokens import get_token_for_user
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@ -176,3 +177,63 @@ def test_change_avatar(client):
avatar.seek(0) avatar.seek(0)
response = client.post(url, post_data) response = client.post(url, post_data)
assert response.status_code == 200 assert response.status_code == 200
def test_list_contacts_private_projects(client):
project = f.ProjectFactory.create()
user_1 = f.UserFactory.create()
user_2 = f.UserFactory.create()
role = f.RoleFactory(project=project, permissions=["view_project"])
membership_1 = f.MembershipFactory.create(project=project, user=user_1, role=role)
membership_2 = f.MembershipFactory.create(project=project, user=user_2, role=role)
url = reverse('users-contacts', kwargs={"pk": user_1.pk})
response = client.get(url, content_type="application/json")
assert response.status_code == 404
client.login(user_1)
response = client.get(url, content_type="application/json")
assert response.status_code == 200
response_content = json.loads(response.content.decode("utf-8"))
assert len(response_content) == 1
assert response_content[0]["id"] == user_2.id
def test_list_contacts_no_projects(client):
user_1 = f.UserFactory.create()
user_2 = f.UserFactory.create()
role_1 = f.RoleFactory(permissions=["view_project"])
role_2 = f.RoleFactory(permissions=["view_project"])
membership_1 = f.MembershipFactory.create(project=role_1.project, user=user_1, role=role_1)
membership_2 = f.MembershipFactory.create(project=role_2.project, user=user_2, role=role_2)
client.login(user_1)
url = reverse('users-contacts', kwargs={"pk": user_1.pk})
response = client.get(url, content_type="application/json")
assert response.status_code == 200
response_content = json.loads(response.content.decode("utf-8"))
assert len(response_content) == 0
def test_list_contacts_public_projects(client):
project = f.ProjectFactory.create(is_private=False,
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)))
user_1 = f.UserFactory.create()
user_2 = f.UserFactory.create()
role = f.RoleFactory(project=project)
membership_1 = f.MembershipFactory.create(project=project, user=user_1, role=role)
membership_2 = f.MembershipFactory.create(project=project, user=user_2, role=role)
url = reverse('users-contacts', kwargs={"pk": user_1.pk})
response = client.get(url, content_type="application/json")
assert response.status_code == 200
response_content = json.loads(response.content.decode("utf-8"))
assert len(response_content) == 2
assert response_content[0]["id"] == user_1.id
assert response_content[1]["id"] == user_2.id