diff --git a/seahub/api2/endpoints/groups.py b/seahub/api2/endpoints/groups.py new file mode 100644 index 0000000000..7a47d98b11 --- /dev/null +++ b/seahub/api2/endpoints/groups.py @@ -0,0 +1,159 @@ +import logging + +from django.utils.dateformat import DateFormat +from django.utils.translation import ugettext as _ +from django.template.defaultfilters import filesizeformat + +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.throttling import UserRateThrottle +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework import status + +import seaserv +from seaserv import seafile_api +from pysearpc import SearpcError + +from seahub.api2.utils import api_error +from seahub.api2.authentication import TokenAuthentication +from seahub.avatar.settings import GROUP_AVATAR_DEFAULT_SIZE +from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url +from seahub.utils import is_org_context +from seahub.utils.timeutils import dt, utc_to_local +from seahub.group.utils import validate_group_name, check_group_name_conflict +from seahub.base.templatetags.seahub_tags import email2nickname, \ + translate_seahub_time + + +logger = logging.getLogger(__name__) + + +class Groups(APIView): + + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated,) + throttle_classes = (UserRateThrottle, ) + + def _get_group_admins(self, group_id): + members = seaserv.get_group_members(group_id) + admin_members = filter(lambda m: m.is_staff, members) + + admins = [] + for u in admin_members: + admins.append(u.user_name) + return admins + + def _can_add_group(self, request): + return request.user.permissions.can_add_group() + + def get(self, request): + """ List all groups. + """ + + org_id = None + username = request.user.username + if is_org_context(request): + org_id = request.user.org.org_id + user_groups = seaserv.get_org_groups_by_user(org_id, username) + else: + user_groups = seaserv.get_personal_groups_by_user(username) + + try: + size = int(request.GET.get('avatar_size', GROUP_AVATAR_DEFAULT_SIZE)) + except ValueError: + size = GROUP_AVATAR_DEFAULT_SIZE + + with_repos = request.GET.get('with_repos') + with_repos = True if with_repos == '1' else False + + groups = [] + for g in user_groups: + val = utc_to_local(dt(g.timestamp)) + avatar_url, is_default, date_uploaded = api_grp_avatar_url(g.id, size) + group = { + "id": g.id, + "name": g.group_name, + "creator": g.creator_name, + "created_at": val.strftime("%Y-%m-%dT%H:%M:%S") + DateFormat(val).format('O'), + "avatar_url": request.build_absolute_uri(avatar_url), + "admins": self._get_group_admins(g.id), + } + + if with_repos: + if org_id: + group_repos = seafile_api.get_org_group_repos(org_id, g.id) + else: + group_repos = seafile_api.get_repos_by_group(g.id) + + repos = [] + for r in group_repos: + repo = { + "id": r.id, + "name": r.name, + "desc": r.desc, + "size": r.size, + "size_formatted": filesizeformat(r.size), + "mtime": r.last_modified, + "mtime_relative": translate_seahub_time(r.last_modified), + "encrypted": r.encrypted, + "permission": r.permission, + "owner": r.user, + "owner_nickname": email2nickname(r.user), + "share_from_me": True if username == r.user else False, + } + repos.append(repo) + + group['repos'] = repos + + groups.append(group) + + return Response(groups) + + def post(self, request): + """ Create a group + """ + if not self._can_add_group(request): + error_msg = _(u'You do not have permission to create group.') + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + username = request.user.username + group_name = request.DATA.get('group_name', '') + group_name = group_name.strip() + + # Check whether group name is validate. + if not validate_group_name(group_name): + error_msg = _(u'Group name can only contain letters, numbers, blank, hyphen or underscore') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # Check whether group name is duplicated. + if check_group_name_conflict(request, group_name): + error_msg = _(u'There is already a group with that name.') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # Group name is valid, create that group. + try: + group_id = seaserv.ccnet_threaded_rpc.create_group(group_name, username) + except SearpcError as e: + logger.error(e) + error_msg = _(u'Failed') + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + try: + size = int(request.DATA.get('avatar_size', GROUP_AVATAR_DEFAULT_SIZE)) + except ValueError: + size = GROUP_AVATAR_DEFAULT_SIZE + + g = seaserv.get_group(group_id) + val = utc_to_local(dt(g.timestamp)) + avatar_url, is_default, date_uploaded = api_grp_avatar_url(g.id, size) + + new_group = { + "id": g.id, + "name": g.group_name, + "creator": g.creator_name, + "created_at": val.strftime("%Y-%m-%dT%H:%M:%S") + DateFormat(val).format('O'), + "avatar_url": request.build_absolute_uri(avatar_url), + "admins": self._get_group_admins(g.id), + } + return Response(new_group, status=status.HTTP_201_CREATED) diff --git a/seahub/group/utils.py b/seahub/group/utils.py index d29a5b8007..be6f9aeaa2 100644 --- a/seahub/group/utils.py +++ b/seahub/group/utils.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- import re +import seaserv + +from seahub.utils import is_org_context + class BadGroupNameError(Exception): pass @@ -17,3 +21,24 @@ def validate_group_name(group_name): return False return re.match('^[\w\s-]+$', group_name, re.U) +def check_group_name_conflict(request, new_group_name): + """Check if new group name conflict with existed group. + + return "True" if conflicted else "False" + """ + org_id = -1 + username = request.user.username + if is_org_context(request): + org_id = request.user.org.org_id + checked_groups = seaserv.get_org_groups_by_user(org_id, username) + else: + if request.cloud_mode: + checked_groups = seaserv.get_personal_groups_by_user(username) + else: + checked_groups = seaserv.ccnet_threaded_rpc.get_all_groups(-1, -1) + + for g in checked_groups: + if g.group_name == new_group_name: + return True + + return False diff --git a/seahub/test_utils.py b/seahub/test_utils.py index b93213ad21..2f53ad1da8 100644 --- a/seahub/test_utils.py +++ b/seahub/test_utils.py @@ -96,8 +96,10 @@ class Fixtures(Exam): group_id = ccnet_threaded_rpc.create_group(group_name, username) return ccnet_threaded_rpc.get_group(group_id) - def remove_group(self): - return ccnet_threaded_rpc.remove_group(self.group.id, self.user.username) + def remove_group(self, group_id=None): + if not group_id: + group_id = self.group.id + return ccnet_threaded_rpc.remove_group(group_id, self.user.username) class BaseTestCase(TestCase, Fixtures): diff --git a/seahub/urls.py b/seahub/urls.py index eec6904072..93606f1c37 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -19,6 +19,7 @@ from seahub.views.wiki import personal_wiki, personal_wiki_pages, \ personal_wiki_page_delete, personal_wiki_use_lib from seahub.views.sysadmin import * from seahub.views.ajax import * +from seahub.api2.endpoints.groups import Groups # Uncomment the next two lines to enable the admin: #from django.contrib import admin @@ -190,6 +191,7 @@ urlpatterns = patterns( ### Apps ### (r'^api2/', include('seahub.api2.urls')), + url(r'^api/v2.1/groups/$', Groups.as_view(), name='api-v2.1-groups'), (r'^avatar/', include('seahub.avatar.urls')), (r'^notification/', include('seahub.notifications.urls')), (r'^contacts/', include('seahub.contacts.urls')), diff --git a/tests/api/endpoints/test_groups.py b/tests/api/endpoints/test_groups.py new file mode 100644 index 0000000000..9f2875fb69 --- /dev/null +++ b/tests/api/endpoints/test_groups.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +import json +from mock import patch + +from django.core.urlresolvers import reverse +from seaserv import seafile_api + +from seahub.test_utils import BaseTestCase +from seahub.api2.endpoints.groups import Groups + +class GroupsTest(BaseTestCase): + + def setUp(self): + self.login_as(self.user) + self.group_id = self.group.id + self.group_name = self.group.group_name + self.repo_id = self.repo.id + + self.url = reverse('api-v2.1-groups') + + # share repo to group + seafile_api.set_group_repo(self.repo_id, + self.group_id, self.user.email, 'rw') + + def tearDown(self): + self.remove_group() + self.remove_repo() + + def test_get_group_info(self): + resp = self.client.get(self.url) + self.assertEqual(200, resp.status_code) + + json_resp = json.loads(resp.content) + assert len(json_resp[0]) == 6 + + group_ids = [] + for group in json_resp: + group_ids.append(group['id']) + + assert self.group_id in group_ids + + def test_get_group_info_with_repos(self): + resp = self.client.get(self.url + '?with_repos=1') + self.assertEqual(200, resp.status_code) + + json_resp = json.loads(resp.content) + assert len(json_resp[0]) == 7 + + group_ids = [] + group_repos = [] + for group in json_resp: + group_ids.append(group['id']) + for repo in group['repos']: + group_repos.append(repo) + + group_repo_ids = [] + for repo in group_repos: + group_repo_ids.append(repo['id']) + + assert self.repo_id in group_repo_ids + assert self.group_id in group_ids + + def test_create_group(self): + new_group_name = 'new-group-1' + + resp = self.client.post(self.url, {'group_name': new_group_name}) + self.assertEqual(201, resp.status_code) + + json_resp = json.loads(resp.content) + assert len(json_resp) == 6 + assert json_resp['name'] == new_group_name + assert json_resp['creator'] == self.user.email + + self.remove_group(json_resp['id']) + + def test_create_group_with_cn_name(self): + new_group_name = u'中文' + resp = self.client.post(self.url, {'group_name': new_group_name}) + self.assertEqual(201, resp.status_code) + + json_resp = json.loads(resp.content) + assert len(json_resp) == 6 + assert json_resp['name'] == new_group_name + assert json_resp['creator'] == self.user.email + + self.remove_group(json_resp['id']) + + def test_can_not_create_group_with_same_name(self): + resp = self.client.post(self.url, {'group_name': self.group_name}) + self.assertEqual(400, resp.status_code) + + def test_can_not_create_group_with_invalid_name(self): + group_name = 'new%group-2' + + resp = self.client.post(self.url, {'group_name': group_name}) + self.assertEqual(400, resp.status_code) + + @patch.object(Groups, '_can_add_group') + def test_can_not_create_group_with_invalid_permission(self, mock_can_add_group): + mock_can_add_group.return_value = False + group_name = 'new-group-3' + + resp = self.client.post(self.url, {'group_name': group_name}) + self.assertEqual(403, resp.status_code)