From 18eaec143cb40b567ff1c8e8722b8da23f8b9208 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Sun, 24 Mar 2013 15:00:01 -0400 Subject: [PATCH] Add related resources from a user object, as a rapid way to list what organizations or projects they belong to. --- lib/main/models/__init__.py | 2 +- lib/main/serializers.py | 23 +++++++++++++++-- lib/main/tests/users.py | 49 ++++++++++++++++++++++++++++++++----- lib/main/views.py | 45 ++++++++++++++++++++++++++++++++++ lib/urls.py | 34 +++++++++++++++---------- 5 files changed, 131 insertions(+), 22 deletions(-) diff --git a/lib/main/models/__init__.py b/lib/main/models/__init__.py index 6000758c8a..2678fac909 100644 --- a/lib/main/models/__init__.py +++ b/lib/main/models/__init__.py @@ -316,7 +316,7 @@ class Team(CommonModel): projects = models.ManyToManyField('Project', blank=True, related_name='teams') users = models.ManyToManyField('auth.User', blank=True, related_name='teams') - organization = models.ManyToManyField('Organization', related_name='teams') + organizations = models.ManyToManyField('Organization', related_name='teams') class Project(CommonModel): ''' diff --git a/lib/main/serializers.py b/lib/main/serializers.py index 681f4222bc..c92d8db93a 100644 --- a/lib/main/serializers.py +++ b/lib/main/serializers.py @@ -78,6 +78,21 @@ class ProjectSerializer(BaseSerializer): # FIXME: add related resources: inventories return dict() +class TeamSerializer(BaseSerializer): + + # add the URL and related resources + url = serializers.CharField(source='get_absolute_url', read_only=True) + related = serializers.SerializerMethodField('get_related') + + class Meta: + model = Team + fields = ('url', 'id', 'related', 'name', 'description', 'creation_date') + + def get_related(self, obj): + # FIXME: add related resources: projects, users, organizations + return dict() + + class UserSerializer(BaseSerializer): # add the URL and related resources @@ -89,8 +104,12 @@ class UserSerializer(BaseSerializer): fields = ('url', 'id', 'username', 'first_name', 'last_name', 'email', 'is_active', 'is_superuser', 'related') def get_related(self, obj): - # FIXME: add related lookups? - return dict() + return dict( + teams = reverse(lib.urls.views_UsersTeamsList, args=(obj.pk,)), + organizations = reverse(lib.urls.views_UsersOrganizationsList, args=(obj.pk,)), + admin_of_organizations = reverse(lib.urls.views_UsersAdminOrganizationsList, args=(obj.pk,)), + ) + def get_absolute_url_override(self, obj): import lib.urls diff --git a/lib/main/tests/users.py b/lib/main/tests/users.py index 6206d00ded..5a8e814645 100644 --- a/lib/main/tests/users.py +++ b/lib/main/tests/users.py @@ -25,6 +25,7 @@ class UsersTest(BaseTest): self.organizations = self.make_organizations(self.super_django_user, 1) self.organizations[0].admins.add(self.normal_django_user) self.organizations[0].users.add(self.other_django_user) + self.organizations[0].users.add(self.normal_django_user) def test_only_super_user_or_org_admin_can_add_users(self): url = '/api/v1/users/' @@ -153,11 +154,47 @@ class UsersTest(BaseTest): self.assertEquals(data['results'][0]['username'], 'admin') self.assertEquals(data['count'], 1) - # TODO: - # possibly nice to have, some quick lookup functions that are not postable: - # /users/2/organizations - # /users/2/projects - # /users/2/teams - + def test_user_related_resources(self): + + # organizations the user is a member of, should be 1 + url = '/api/v1/users/2/organizations/' + data = self.get(url, expect=200, auth=self.get_normal_credentials()) + self.assertEquals(data['count'], 1) + # also accessible via superuser + data = self.get(url, expect=200, auth=self.get_super_credentials()) + self.assertEquals(data['count'], 1) + # but not by other user + data = self.get(url, expect=403, auth=self.get_other_credentials()) + + # organizations the user is an admin of, should be 1 + url = '/api/v1/users/2/admin_of_organizations/' + data = self.get(url, expect=200, auth=self.get_normal_credentials()) + self.assertEquals(data['count'], 1) + # also accessible via superuser + data = self.get(url, expect=200, auth=self.get_super_credentials()) + self.assertEquals(data['count'], 1) + # but not by other user + data = self.get(url, expect=403, auth=self.get_other_credentials()) + + # teams the user is on, should be 0 + url = '/api/v1/users/2/teams/' + data = self.get(url, expect=200, auth=self.get_normal_credentials()) + self.assertEquals(data['count'], 0) + # also accessible via superuser + data = self.get(url, expect=200, auth=self.get_super_credentials()) + self.assertEquals(data['count'], 0) + # but not by other user + data = self.get(url, expect=403, auth=self.get_other_credentials()) + + # verify org admin can still read other user data too + url = '/api/v1/users/3/organizations/' + data = self.get(url, expect=200, auth=self.get_normal_credentials()) + self.assertEquals(data['count'], 1) + url = '/api/v1/users/3/admin_of_organizations/' + data = self.get(url, expect=200, auth=self.get_normal_credentials()) + self.assertEquals(data['count'], 0) + url = '/api/v1/users/3/teams/' + data = self.get(url, expect=200, auth=self.get_normal_credentials()) + self.assertEquals(data['count'], 0) diff --git a/lib/main/views.py b/lib/main/views.py index 3f726c87d5..698e33b8f4 100644 --- a/lib/main/views.py +++ b/lib/main/views.py @@ -193,6 +193,51 @@ class UsersMeList(BaseList): ''' a quick way to find my user record ''' return User.objects.filter(pk=self.request.user.pk) +class UsersTeamsList(BaseSubList): + + model = Team + serializer_class = TeamSerializer + permission_classes = (CustomRbac,) + parent_model = User + relationship = 'teams' + postable = False + + def _get_queryset(self): + user = User.objects.get(pk=self.kwargs['pk']) + if not UserHelper.can_user_administrate(self.request.user, user): + raise PermissionDenied() + return Team.objects.filter(users__in = [ user ]) + +class UsersOrganizationsList(BaseSubList): + + model = Organization + serializer_class = OrganizationSerializer + permission_classes = (CustomRbac,) + parent_model = User + relationship = 'organizations' + postable = False + + def _get_queryset(self): + user = User.objects.get(pk=self.kwargs['pk']) + if not UserHelper.can_user_administrate(self.request.user, user): + raise PermissionDenied() + return Organization.objects.filter(users__in = [ user ]) + +class UsersAdminOrganizationsList(BaseSubList): + + model = Organization + serializer_class = OrganizationSerializer + permission_classes = (CustomRbac,) + parent_model = User + relationship = 'admin_of_organizations' + postable = False + + def _get_queryset(self): + user = User.objects.get(pk=self.kwargs['pk']) + if not UserHelper.can_user_administrate(self.request.user, user): + raise PermissionDenied() + return Organization.objects.filter(admins__in = [ user ]) + class UsersDetail(BaseDetail): model = User diff --git a/lib/urls.py b/lib/urls.py index 1b2a28e41e..4e4e84b90c 100644 --- a/lib/urls.py +++ b/lib/urls.py @@ -20,18 +20,23 @@ from django.conf.urls import * import lib.main.views as views # organizations service -views_OrganizationsList = views.OrganizationsList.as_view() -views_OrganizationsDetail = views.OrganizationsDetail.as_view() -views_OrganizationsAuditTrailList = views.OrganizationsAuditTrailList.as_view() -views_OrganizationsUsersList = views.OrganizationsUsersList.as_view() -views_OrganizationsAdminsList = views.OrganizationsAdminsList.as_view() -views_OrganizationsProjectsList = views.OrganizationsProjectsList.as_view() -views_OrganizationsTagsList = views.OrganizationsTagsList.as_view() +views_OrganizationsList = views.OrganizationsList.as_view() +views_OrganizationsDetail = views.OrganizationsDetail.as_view() +views_OrganizationsAuditTrailList = views.OrganizationsAuditTrailList.as_view() +views_OrganizationsUsersList = views.OrganizationsUsersList.as_view() +views_OrganizationsAdminsList = views.OrganizationsAdminsList.as_view() +views_OrganizationsProjectsList = views.OrganizationsProjectsList.as_view() +views_OrganizationsTagsList = views.OrganizationsTagsList.as_view() # users service -views_UsersList = views.UsersList.as_view() -views_UsersDetail = views.UsersDetail.as_view() -views_UsersMeList = views.UsersMeList.as_view() +views_UsersList = views.UsersList.as_view() +views_UsersDetail = views.UsersDetail.as_view() +views_UsersMeList = views.UsersMeList.as_view() +views_UsersTeamsList = views.UsersTeamsList.as_view() +views_UsersOrganizationsList = views.UsersOrganizationsList.as_view() +views_UsersAdminOrganizationsList = views.UsersAdminOrganizationsList.as_view() + + # projects service views_ProjectsDetail = views.OrganizationsDetail.as_view() @@ -69,9 +74,12 @@ urlpatterns = patterns('', url(r'^api/v1/organizations/(?P[0-9]+)/tags/$', views_OrganizationsTagsList), # users service - url(r'^api/v1/users/$', views_UsersList), - url(r'^api/v1/users/(?P[0-9]+)/$', views_UsersDetail), - url(r'^api/v1/me/$', views_UsersMeList), + url(r'^api/v1/users/$', views_UsersList), + url(r'^api/v1/users/(?P[0-9]+)/$', views_UsersDetail), + url(r'^api/v1/me/$', views_UsersMeList), + url(r'^api/v1/users/(?P[0-9]+)/teams/$', views_UsersTeamsList), + url(r'^api/v1/users/(?P[0-9]+)/organizations/$', views_UsersOrganizationsList), + url(r'^api/v1/users/(?P[0-9]+)/admin_of_organizations/$', views_UsersAdminOrganizationsList), # projects service url(r'^api/v1/projects/(?P[0-9]+)/$', views_ProjectsDetail),