From 56f533c300a8fab117f0f22e02c23fecd2c2c7d5 Mon Sep 17 00:00:00 2001 From: zhengxie Date: Tue, 2 Feb 2016 15:01:13 +0800 Subject: [PATCH] [api2] Add list/post/delete group discussions --- seahub/api2/endpoints/group_discussion.py | 42 +++++++++ seahub/api2/endpoints/group_discussions.py | 88 +++++++++++++++++++ seahub/api2/permissions.py | 13 ++- seahub/api2/urls.py | 4 + tests/api/endpoints/test_group_discussion.py | 31 +++++++ tests/api/endpoints/test_group_discussions.py | 76 ++++++++++++++++ 6 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 seahub/api2/endpoints/group_discussion.py create mode 100644 seahub/api2/endpoints/group_discussions.py create mode 100644 tests/api/endpoints/test_group_discussion.py create mode 100644 tests/api/endpoints/test_group_discussions.py diff --git a/seahub/api2/endpoints/group_discussion.py b/seahub/api2/endpoints/group_discussion.py new file mode 100644 index 0000000000..4642acc7e9 --- /dev/null +++ b/seahub/api2/endpoints/group_discussion.py @@ -0,0 +1,42 @@ +from rest_framework import status +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +from seaserv import ccnet_api + +from seahub.api2.authentication import TokenAuthentication +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.utils import api_error +from seahub.group.models import GroupMessage +from .utils import api_check_group + +json_content_type = 'application/json; charset=utf-8' + +class GroupDiscussion(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated,) + throttle_classes = (UserRateThrottle, ) + + @api_check_group + def delete(self, request, group_id, discuss_id, format=None): + """Remove a group discussion. + Only discussion creator or group admin can perform this op. + """ + username = request.user.username + group_id = int(group_id) + + try: + discussion = GroupMessage.objects.get(pk=discuss_id) + except GroupMessage.DoesNotExist: + return api_error(status.HTTP_400_BAD_REQUEST, 'Bad discussion id') + + # perm check + if not ccnet_api.check_group_staff(group_id, username) and \ + discussion.from_email != username: + return api_error(status.HTTP_403_FORBIDDEN, 'Permission error.') + + discussion.delete() + + return Response(status=204) diff --git a/seahub/api2/endpoints/group_discussions.py b/seahub/api2/endpoints/group_discussions.py new file mode 100644 index 0000000000..276cb7d5af --- /dev/null +++ b/seahub/api2/endpoints/group_discussions.py @@ -0,0 +1,88 @@ +import json + +from django.core.paginator import EmptyPage, InvalidPage +from django.http import HttpResponse +from django.utils.dateformat import DateFormat + +from rest_framework import status +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +from seahub.api2.authentication import TokenAuthentication +from seahub.api2.permissions import IsGroupMember +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.utils import api_error +from seahub.group.models import GroupMessage +from seahub.utils.paginator import Paginator +from .utils import api_check_group + +json_content_type = 'application/json; charset=utf-8' + +class GroupDiscussions(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated, IsGroupMember) + throttle_classes = (UserRateThrottle, ) + + @api_check_group + def get(self, request, group_id, format=None): + """List all group discussions. Only group members can perform this op. + """ + # 1 <= page, defaults to 1 + try: + page = int(request.GET.get('page', '1')) + except ValueError: + page = 1 + if page < 0: + page = 1 + + # 1 <= per_page <= 100, defaults to 20 + try: + per_page = int(request.GET.get('per_page', '20')) + except ValueError: + per_page = 20 + if per_page < 1 or per_page > 100: + per_page = 20 + + paginator = Paginator(GroupMessage.objects.filter( + group_id=group_id).order_by('-timestamp'), per_page) + + try: + group_msgs = paginator.page(page) + except (EmptyPage, InvalidPage): + group_msgs = paginator.page(paginator.num_pages) + + msgs = [] + for e in group_msgs: + msgs.append({ + "group_id": group_id, + "discussion_id": e.pk, + "user": e.from_email, + "content": e.message, + "created_at": e.timestamp.strftime("%Y-%m-%dT%H:%M:%S") + DateFormat(e.timestamp).format('O'), + }) + + return HttpResponse(json.dumps(msgs), status=200, + content_type=json_content_type) + + @api_check_group + def post(self, request, group_id, format=None): + """Post a group discussions. Only group members can perform this op. + """ + content = request.data.get('content', '') + if not content: + return api_error(status.HTTP_400_BAD_REQUEST, 'content can not be empty') + + username = request.user.username + discuss = GroupMessage.objects.create(group_id=group_id, + from_email=username, + message=content) + + return Response({ + "group_id": group_id, + "discussion_id": discuss.pk, + "user": username, + "content": discuss.message, + "created_at": discuss.timestamp.strftime("%Y-%m-%dT%H:%M:%S") + DateFormat(discuss.timestamp).format('O'), + }, status=201) diff --git a/seahub/api2/permissions.py b/seahub/api2/permissions.py index 944fabab92..b4c0e57c84 100644 --- a/seahub/api2/permissions.py +++ b/seahub/api2/permissions.py @@ -4,7 +4,7 @@ Provides a set of pluggable permission policies. from rest_framework.permissions import BasePermission -from seaserv import check_permission, is_repo_owner +from seaserv import check_permission, is_repo_owner, ccnet_api SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS'] @@ -43,4 +43,13 @@ class IsRepoOwner(BasePermission): user = request.user.username if request.user else '' return True if is_repo_owner(user, repo_id) else False - + + +class IsGroupMember(BasePermission): + """ + Check whether user is in a group. + """ + def has_permission(self, request, view, obj=None): + group_id = int(view.kwargs.get('group_id', '')) + username = request.user.username if request.user else '' + return True if ccnet_api.is_group_user(group_id, username) else False diff --git a/seahub/api2/urls.py b/seahub/api2/urls.py index 8bb800abaf..390d60d421 100644 --- a/seahub/api2/urls.py +++ b/seahub/api2/urls.py @@ -8,6 +8,8 @@ from .endpoints.account import Account from .endpoints.shared_upload_links import SharedUploadLinksView from .endpoints.be_shared_repo import BeSharedReposView from .endpoints.search_user import SearchUser +from .endpoints.group_discussions import GroupDiscussions +from .endpoints.group_discussion import GroupDiscussion urlpatterns = patterns('', url(r'^ping/$', Ping.as_view()), @@ -87,6 +89,8 @@ urlpatterns = patterns('', url(r'^groups/(?P\d+)/members/$', GroupMembers.as_view()), url(r'^groups/(?P\d+)/repos/$', GroupRepos.as_view(), name="api2-grouprepos"), url(r'^groups/(?P\d+)/repos/(?P[-0-9a-f]{36})/$', GroupRepo.as_view(), name="api2-grouprepo"), + url(r'^groups/(?P\d+)/discussions/$', GroupDiscussions.as_view(), name="api2-group-discussions"), + url(r'^groups/(?P\d+)/discussions/(?P\d+)/$', GroupDiscussion.as_view(), name="api2-group-discussion"), url(r'^html/events/$', EventsHtml.as_view()), url(r'^html/more_events/$', AjaxEvents.as_view(), name="more_events"), diff --git a/tests/api/endpoints/test_group_discussion.py b/tests/api/endpoints/test_group_discussion.py new file mode 100644 index 0000000000..ab2fe509bb --- /dev/null +++ b/tests/api/endpoints/test_group_discussion.py @@ -0,0 +1,31 @@ +import json + +from django.core.urlresolvers import reverse + +from seahub.group.models import GroupMessage +from seahub.test_utils import BaseTestCase + +class GroupDiscussionTest(BaseTestCase): + def setUp(self): + self.username = self.user.username + self.login_as(self.user) + self.discuss = GroupMessage.objects.create(group_id=self.group.id, + from_email=self.username, + message="msg 1") + self.endpoint = reverse('api2-group-discussion', args=[ + self.group.id, self.discuss.pk]) + + def test_can_delete_discussion(self): + assert len(GroupMessage.objects.all()) == 1 + + resp = self.client.delete(self.endpoint) + self.assertEqual(204, resp.status_code) + + assert len(GroupMessage.objects.all()) == 0 + + def test_can_not_delete_discussion_when_invalid_user(self): + self.logout() + + self.login_as(self.admin) + resp = self.client.delete(self.endpoint) + self.assertEqual(403, resp.status_code) diff --git a/tests/api/endpoints/test_group_discussions.py b/tests/api/endpoints/test_group_discussions.py new file mode 100644 index 0000000000..1165d02b4a --- /dev/null +++ b/tests/api/endpoints/test_group_discussions.py @@ -0,0 +1,76 @@ +import json + +from django.core.urlresolvers import reverse + +from seahub.group.models import GroupMessage +from seahub.test_utils import BaseTestCase + +class GroupDiscussionsTest(BaseTestCase): + def setUp(self): + self.login_as(self.user) + self.endpoint = reverse('api2-group-discussions', args=[self.group.id]) + self.username = self.user.username + + def test_can_list(self): + GroupMessage(group_id=self.group.id, from_email=self.username, + message="msg 1").save() + + resp = self.client.get(self.endpoint) + self.assertEqual(200, resp.status_code) + json_resp = json.loads(resp.content) + assert len(json_resp) == 1 + + def test_can_list_with_paginator(self): + for i in range(10): + GroupMessage(group_id=self.group.id, from_email=self.username, + message="msg %s" % i).save() + + resp = self.client.get(self.endpoint + '?page=1&per_page=5') + self.assertEqual(200, resp.status_code) + json_resp = json.loads(resp.content) + assert len(json_resp) == 5 + assert json_resp[0]['content'] == 'msg 9' + + resp = self.client.get(self.endpoint + '?page=2&per_page=5') + json_resp = json.loads(resp.content) + assert len(json_resp) == 5 + assert json_resp[-1]['content'] == 'msg 0' + + resp = self.client.get(self.endpoint + '?page=3&per_page=5') + json_resp = json.loads(resp.content) + assert len(json_resp) == 5 + assert json_resp[-1]['content'] == 'msg 0' + + def test_can_not_list_when_invalid_user(self): + self.logout() + + self.login_as(self.admin) + resp = self.client.get(self.endpoint) + self.assertEqual(403, resp.status_code) + + def test_can_post_a_discussion(self): + assert len(GroupMessage.objects.all()) == 0 + resp = self.client.post(self.endpoint, { + 'content': 'msg 1' + }) + self.assertEqual(201, resp.status_code) + json_resp = json.loads(resp.content) + + assert len(GroupMessage.objects.all()) == 1 + assert json_resp['content'] == 'msg 1' + + def test_can_not_post_empty_content(self): + resp = self.client.post(self.endpoint, { + 'content': '' + }) + self.assertEqual(400, resp.status_code) + + def test_can_not_post_content_when_invalid_user(self): + self.logout() + + self.login_as(self.admin) + resp = self.client.post(self.endpoint, { + 'content': 'msg 1' + }) + self.assertEqual(403, resp.status_code) +