Merge pull request #312 from taigaio/user-contacts-API
User contacts APIremotes/origin/enhancement/email-actions
commit
f0d53c7bd7
|
@ -44,6 +44,7 @@ from djmail.template_mail import InlineCSSTemplateMail
|
|||
from . import models
|
||||
from . import serializers
|
||||
from . import permissions
|
||||
from . import filters as user_filters
|
||||
from .signals import user_cancel_account as user_cancel_account_signal
|
||||
|
||||
|
||||
|
@ -51,7 +52,7 @@ class UsersViewSet(ModelCrudViewSet):
|
|||
permission_classes = (permissions.UserPermission,)
|
||||
admin_serializer_class = serializers.UserAdminSerializer
|
||||
serializer_class = serializers.UserSerializer
|
||||
queryset = models.User.objects.all()
|
||||
queryset = models.User.objects.all().prefetch_related("memberships")
|
||||
filter_backends = (MembersFilterBackend,)
|
||||
|
||||
def get_serializer_class(self):
|
||||
|
@ -77,6 +78,23 @@ class UsersViewSet(ModelCrudViewSet):
|
|||
|
||||
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"])
|
||||
def password_recovery(self, request, pk=None):
|
||||
username_or_email = request.DATA.get('username', None)
|
||||
|
|
|
@ -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()
|
|
@ -43,6 +43,7 @@ class UserPermission(TaigaResourcePermission):
|
|||
remove_avatar_perms = IsAuthenticated()
|
||||
starred_perms = AllowAny()
|
||||
change_email_perms = IsTheSameUser()
|
||||
contacts_perms = AllowAny()
|
||||
|
||||
|
||||
class RolesPermission(TaigaResourcePermission):
|
||||
|
|
|
@ -20,7 +20,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from taiga.base.api import serializers
|
||||
from taiga.base.fields import PgArrayField
|
||||
|
||||
from taiga.projects.models import Project
|
||||
from .models import User, Role
|
||||
from .services import get_photo_or_gravatar_url, get_big_photo_or_gravatar_url
|
||||
|
||||
|
@ -31,10 +31,18 @@ import re
|
|||
## User
|
||||
######################################################
|
||||
|
||||
class ContactProjectDetailSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Project
|
||||
fields = ("id", "slug", "name")
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
full_name_display = serializers.SerializerMethodField("get_full_name_display")
|
||||
photo = serializers.SerializerMethodField("get_photo")
|
||||
big_photo = serializers.SerializerMethodField("get_big_photo")
|
||||
roles = serializers.SerializerMethodField("get_roles")
|
||||
projects_with_me = serializers.SerializerMethodField("get_projects_with_me")
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
|
@ -42,7 +50,7 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
# with this info (including there the email)
|
||||
fields = ("id", "username", "full_name", "full_name_display",
|
||||
"color", "bio", "lang", "timezone", "is_active",
|
||||
"photo", "big_photo")
|
||||
"photo", "big_photo", "roles", "projects_with_me")
|
||||
read_only_fields = ("id",)
|
||||
|
||||
def validate_username(self, attrs, source):
|
||||
|
@ -72,6 +80,22 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
def get_big_photo(self, 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 Meta:
|
||||
|
|
|
@ -8,6 +8,7 @@ from .. import factories as f
|
|||
from taiga.base.utils import json
|
||||
from taiga.users import models
|
||||
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
|
||||
|
||||
|
@ -176,3 +177,63 @@ def test_change_avatar(client):
|
|||
avatar.seek(0)
|
||||
response = client.post(url, post_data)
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue