From 3483783a282867dcafd06dbad5275157f69b7285 Mon Sep 17 00:00:00 2001 From: lian Date: Fri, 12 May 2017 18:16:59 +0800 Subject: [PATCH 1/4] add create user avatar api --- seahub/api2/urls.py | 2 +- seahub/api2/views.py | 50 ++++++++++++++++++++++++++++++++++++---- tests/api/test_avatar.py | 19 +++++++++++---- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/seahub/api2/urls.py b/seahub/api2/urls.py index fa09710578..33284ac17c 100644 --- a/seahub/api2/urls.py +++ b/seahub/api2/urls.py @@ -90,7 +90,7 @@ urlpatterns = patterns('', url(r'^repo_history_changes/(?P[-0-9a-f]{36})/$', RepoHistoryChange.as_view()), url(r'^unseen_messages/$', UnseenMessagesCountView.as_view()), - url(r'^avatars/user/(?P\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/resized/(?P[0-9]+)/$', UserAvatarView.as_view()), + url(r'^avatars/user/(?P\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/resized/(?P[0-9]+)/$', UserAvatarView.as_view(), name="api2-user-avatar"), url(r'^avatars/group/(?P\d+)/resized/(?P[0-9]+)/$', GroupAvatarView.as_view()), url(r'^groups/$', Groups.as_view()), diff --git a/seahub/api2/views.py b/seahub/api2/views.py index 9ea7d0b1f7..bc2269157d 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -42,9 +42,13 @@ from .utils import get_diff_details, \ from seahub.api2.base import APIView from seahub.api2.models import TokenV2, DESKTOP_PLATFORMS +from seahub.avatar.models import Avatar +from seahub.avatar.signals import avatar_updated from seahub.avatar.templatetags.avatar_tags import api_avatar_url, avatar -from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, \ - grp_avatar +from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, grp_avatar +from seahub.avatar.settings import (AVATAR_MAX_AVATARS_PER_USER, + AVATAR_MAX_SIZE, AVATAR_ALLOWED_FILE_EXTS) + from seahub.base.accounts import User from seahub.base.models import UserStarredFiles, DeviceToken from seahub.base.templatetags.seahub_tags import email2nickname, \ @@ -3844,9 +3848,9 @@ class GroupRepo(APIView): content_type=json_content_type) class UserAvatarView(APIView): - authentication_classes = (TokenAuthentication, ) + authentication_classes = (TokenAuthentication, SessionAuthentication) permission_classes = (IsAuthenticated,) - throttle_classes = (UserRateThrottle, ) + throttle_classes = (UserRateThrottle,) def get(self, request, user, size, format=None): url, is_default, date_uploaded = api_avatar_url(user, int(size)) @@ -3856,6 +3860,44 @@ class UserAvatarView(APIView): "mtime": get_timestamp(date_uploaded) } return Response(ret) + def post(self, request, user, size, format=None): + + image_file = request.FILES.get('avatar', None) + if not image_file: + error_msg = 'avatar invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + (root, ext) = os.path.splitext(image_file.name.lower()) + if AVATAR_ALLOWED_FILE_EXTS and ext not in AVATAR_ALLOWED_FILE_EXTS: + error_msg = _(u"%(ext)s is an invalid file extension. Authorized extensions are : %(valid_exts_list)s") % {'ext' : ext, 'valid_exts_list' : ", ".join(AVATAR_ALLOWED_FILE_EXTS)} + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if image_file.size > AVATAR_MAX_SIZE: + error_msg = _(u"Your file is too big (%(size)s), the maximum allowed size is %(max_valid_size)s") % { 'size' : filesizeformat(image_file.size), 'max_valid_size' : filesizeformat(AVATAR_MAX_SIZE)} + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + username = request.user.username + count = Avatar.objects.filter(emailuser=username).count() + if AVATAR_MAX_AVATARS_PER_USER > 1 and count >= AVATAR_MAX_AVATARS_PER_USER: + error_msg = _(u"You already have %(nb_avatars)d avatars, and the maximum allowed is %(nb_max_avatars)d.") % { 'nb_avatars' : count, 'nb_max_avatars' : AVATAR_MAX_AVATARS_PER_USER} + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + avatar = Avatar( + emailuser = username, + primary = True, + ) + avatar.avatar.save(image_file.name, image_file) + avatar.save() + avatar_updated.send(sender=Avatar, user=request.user, avatar=avatar) + + url, is_default, date_uploaded = api_avatar_url(request.user, int(size)) + ret = { + "url": request.build_absolute_uri(url), + "mtime": get_timestamp(date_uploaded) + } + + return Response(ret) + class GroupAvatarView(APIView): authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) diff --git a/tests/api/test_avatar.py b/tests/api/test_avatar.py index 4cda2996e5..c06e414179 100644 --- a/tests/api/test_avatar.py +++ b/tests/api/test_avatar.py @@ -1,18 +1,27 @@ -import unittest - +import os from tests.api.apitestbase import ApiTestBase from tests.api.urls import AVATAR_BASE_URL, GROUPS_URL -from tests.common.utils import randstring, apiurl, urljoin +from tests.common.utils import randstring, urljoin class AvatarApiTest(ApiTestBase): - def test_user_avatar(self): + + def test_get_user_avatar(self): avatar_url = urljoin(AVATAR_BASE_URL, 'user', self.username, '/resized/80/') info = self.get(avatar_url).json() self.assertIsNotNone(info['url']) self.assertIsNotNone(info['is_default']) self.assertIsNotNone(info['mtime']) - def test_group_avatar(self): + def test_create_user_avatar(self): + avatar_url = urljoin(AVATAR_BASE_URL, 'user', self.username, '/resized/80/') + avatar_file = os.path.join(os.getcwd(), 'media/img/seafile-logo.png') + + with open(avatar_file) as f: + json_resp = self.post(avatar_url, files={'avatar': f}).json() + + assert 'media/avatars' in json_resp['url'] + + def test_get_group_avatar(self): gname = randstring(16) data = {'group_name': gname} res = self.put(GROUPS_URL, data=data) From f062ed4be47a1ffaf5339f3f568e049e4afab890 Mon Sep 17 00:00:00 2001 From: lian Date: Sat, 13 May 2017 09:43:24 +0800 Subject: [PATCH 2/4] add v2.1 repos api batch share repos to user/group --- seahub/api2/endpoints/repos_batch.py | 270 ++++++++++++++++++++++++ seahub/urls.py | 2 + tests/api/endpoints/test_repos_batch.py | 195 +++++++++++++++++ 3 files changed, 467 insertions(+) create mode 100644 seahub/api2/endpoints/repos_batch.py create mode 100644 tests/api/endpoints/test_repos_batch.py diff --git a/seahub/api2/endpoints/repos_batch.py b/seahub/api2/endpoints/repos_batch.py new file mode 100644 index 0000000000..55e6ab944b --- /dev/null +++ b/seahub/api2/endpoints/repos_batch.py @@ -0,0 +1,270 @@ +# Copyright (c) 2012-2016 Seafile Ltd. +import logging + +from pysearpc import SearpcError +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 +import seaserv +from seaserv import seafile_api, ccnet_api + +from seahub.api2.authentication import TokenAuthentication +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.utils import api_error +from seahub.base.accounts import User +from seahub.share.signals import share_repo_to_user_successful, \ + share_repo_to_group_successful +from seahub.utils import (is_org_context, send_perm_audit_msg) + +logger = logging.getLogger(__name__) + +class ReposBatchView(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated,) + throttle_classes = (UserRateThrottle,) + + def get_repo_shared_to_users(self, request, repo_id): + username = request.user.username + + if is_org_context(request): + org_id = request.user.org.org_id + share_items = seafile_api.list_org_repo_shared_to(org_id, username, repo_id) + else: + share_items = seafile_api.list_repo_shared_to(username, repo_id) + + ret = [] + for item in share_items: + ret.append(item.user) + + return ret + + def has_shared_to_user(self, request, repo_id, username): + users = self.get_repo_shared_to_users(request, repo_id) + + has_shared = False + if username in users: + has_shared = True + + return has_shared + + def get_repo_shared_to_groups(self, request, repo_id): + username = request.user.username + if is_org_context(request): + org_id = request.user.org.org_id + share_items = seafile_api.list_org_repo_shared_group(org_id, + username, repo_id) + else: + share_items = seafile_api.list_repo_shared_group_by_user( + username, repo_id) + + ret = [] + for item in share_items: + ret.append(item.group_id) + + return ret + + def has_shared_to_group(self, request, repo_id, group_id): + group_ids = self.get_repo_shared_to_groups(request, repo_id) + + has_shared = False + if group_id in group_ids: + has_shared = True + + return has_shared + + def post(self, request): + + # argument check + operation = request.data.get('operation') + + # operation could be `share`, `delete`, `transfer` + # we now only use `share` + if not operation or operation not in ('share'): + error_msg = 'operation invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + result = {} + result['failed'] = [] + result['success'] = [] + + username = request.user.username + repo_id_list = request.data.getlist('repo_id') + valid_repo_id_list = [] + + # filter out invalid repo id + for repo_id in repo_id_list: + + if not seafile_api.get_repo(repo_id): + result['failed'].append({ + 'repo_id': repo_id, + 'error_msg': 'Library %s not found.' % repo_id + }) + continue + + if is_org_context(request): + org_id = request.user.org.org_id + org_repo_owner = seafile_api.get_org_repo_owner(repo_id) + if not username == org_repo_owner: + result['failed'].append({ + 'repo_id': repo_id, + 'error_msg': 'Permission denied.' + }) + continue + else: + if not seafile_api.is_repo_owner(username, repo_id): + result['failed'].append({ + 'repo_id': repo_id, + 'error_msg': 'Permission denied.' + }) + continue + + valid_repo_id_list.append(repo_id) + + # share repo + if operation == 'share': + + share_type = request.data.get('share_type') + if share_type != 'user' and share_type != 'group': + error_msg = 'share_type invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + permission = request.data.get('permission', 'rw') + if permission not in ('r', 'rw'): + error_msg = 'permission invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # share repo to user + if share_type == 'user': + to_username = request.data.get('username', None) + if not to_username: + error_msg = 'username invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + try: + User.objects.get(email=to_username) + except User.DoesNotExist: + error_msg = 'User %s not found.' % to_username + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + # check if to_user is an org user + try: + org_of_to_user = ccnet_api.get_orgs_by_user(to_username) + except Exception as e: + logger.debug(e) + org_of_to_user = [] + + if is_org_context(request): + org_id = request.user.org.org_id + org_name = request.user.org.org_name + if len(org_of_to_user) == 0 or org_id != org_of_to_user[0].org_id: + error_msg = 'User %s is not member of organization %s.' \ + % (to_username, org_name) + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + else: + if len(org_of_to_user) >= 1: + error_msg = 'User %s is member of organization %s.' \ + % (to_username, org_of_to_user[0].org_name) + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + for repo_id in valid_repo_id_list: + if self.has_shared_to_user(request, repo_id, to_username): + result['failed'].append({ + 'repo_id': repo_id, + 'error_msg': 'This item has been shared to %s.' % to_username + }) + continue + + try: + if is_org_context(request): + org_id = request.user.org.org_id + seaserv.seafserv_threaded_rpc.org_add_share(org_id, + repo_id, username, to_username, permission) + else: + seafile_api.share_repo( + repo_id, username, to_username, permission) + + # send a signal when sharing repo successful + repo = seafile_api.get_repo(repo_id) + share_repo_to_user_successful.send(sender=None, + from_user=username, to_user=to_username, repo=repo) + + result['success'].append({ + "repo_id": repo_id, + "username": to_username, + "permission": permission + }) + + send_perm_audit_msg('add-repo-perm', username, to_username, + repo_id, '/', permission) + except Exception as e: + logger.error(e) + result['failed'].append({ + 'repo_id': repo_id, + 'error_msg': 'Internal Server Error' + }) + + # share repo to group + if share_type == 'group': + to_group_id = request.data.get('group_id', None) + if not to_group_id: + error_msg = 'group_id invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + try: + to_group_id = int(to_group_id) + except ValueError: + error_msg = 'group_id invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + group = ccnet_api.get_group(to_group_id) + if not group: + error_msg = 'Group %s not found.' % to_group_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + group_name = group.group_name + if not ccnet_api.is_group_user(to_group_id, username): + error_msg = 'User %s is not member of group %s.' % (username, group_name) + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + for repo_id in valid_repo_id_list: + if self.has_shared_to_group(request, repo_id, to_group_id): + result['failed'].append({ + 'repo_id': repo_id, + 'error_msg': 'This item has been shared to %s.' % group_name + }) + continue + + try: + if is_org_context(request): + org_id = request.user.org.org_id + seafile_api.add_org_group_repo( + repo_id, org_id, to_group_id, username, permission) + else: + seafile_api.set_group_repo( + repo_id, to_group_id, username, permission) + + # send a signal when sharing repo successful + repo = seafile_api.get_repo(repo_id) + share_repo_to_group_successful.send(sender=None, + from_user=username, group_id=to_group_id, repo=repo) + + result['success'].append({ + "repo_id": repo_id, + "group_id": to_group_id, + "group_name": group_name, + "permission": permission + }) + + send_perm_audit_msg('add-repo-perm', username, to_group_id, + repo_id, '/', permission) + + except SearpcError as e: + logger.error(e) + result['failed'].append({ + 'repo_id': repo_id, + 'error_msg': 'Internal Server Error' + }) + + return Response(result) diff --git a/seahub/urls.py b/seahub/urls.py index cbf6812954..79381320ec 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -26,6 +26,7 @@ from seahub.api2.endpoints.share_links import ShareLinks, ShareLink from seahub.api2.endpoints.shared_folders import SharedFolders from seahub.api2.endpoints.shared_repos import SharedRepos, SharedRepo from seahub.api2.endpoints.upload_links import UploadLinks, UploadLink +from seahub.api2.endpoints.repos_batch import ReposBatchView from seahub.api2.endpoints.file import FileView from seahub.api2.endpoints.dir import DirView from seahub.api2.endpoints.repo_trash import RepoTrash @@ -212,6 +213,7 @@ urlpatterns = patterns( url(r'^api/v2.1/upload-links/(?P[a-f0-9]{10})/$', UploadLink.as_view(), name='api-v2.1-upload-link'), ## user::repos + url(r'^api/v2.1/repos/batch/$', ReposBatchView.as_view(), name='api-v2.1-repos-batch'), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/file/$', FileView.as_view(), name='api-v2.1-file-view'), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/dir/$', DirView.as_view(), name='api-v2.1-dir-view'), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/trash/$', RepoTrash.as_view(), name='api-v2.1-repo-trash'), diff --git a/tests/api/endpoints/test_repos_batch.py b/tests/api/endpoints/test_repos_batch.py new file mode 100644 index 0000000000..f4d76ff0c6 --- /dev/null +++ b/tests/api/endpoints/test_repos_batch.py @@ -0,0 +1,195 @@ +import json +from seaserv import seafile_api, ccnet_api +from django.core.urlresolvers import reverse +from tests.common.utils import randstring +from seahub.test_utils import BaseTestCase + +class ReposBatchViewTest(BaseTestCase): + + def create_new_repo(self, username): + new_repo_id = seafile_api.create_repo(name=randstring(10), + desc='', username=username, passwd=None) + + return new_repo_id + + def setUp(self): + self.user_name = self.user.username + self.admin_name = self.admin.username + + self.repo_id = self.repo.id + self.group_id = self.group.id + + self.url = reverse('api-v2.1-repos-batch') + + def tearDown(self): + self.remove_repo() + self.remove_group() + + def test_can_share_repos_to_user(self): + tmp_repo_id = self.create_new_repo(self.user_name) + + self.login_as(self.user) + + data = { + 'operation': 'share', + 'share_type': 'user', + 'username': self.admin_name, + 'repo_id': [self.repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(200, resp.status_code) + json_resp = json.loads(resp.content) + assert len(json_resp['success']) == 1 + assert len(json_resp['failed']) == 0 + + # share repo again will failed + data = { + 'operation': 'share', + 'share_type': 'user', + 'username': self.admin_name, + 'repo_id': [self.repo_id, tmp_repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(200, resp.status_code) + json_resp = json.loads(resp.content) + assert len(json_resp['success']) == 1 + assert len(json_resp['failed']) == 1 + assert self.repo_id in json_resp['failed'][0]['repo_id'] + + self.remove_repo(tmp_repo_id) + + def test_can_share_repos_to_group(self): + tmp_repo_id = self.create_new_repo(self.user_name) + + self.login_as(self.user) + + data = { + 'operation': 'share', + 'share_type': 'group', + 'group_id': self.group_id, + 'repo_id': [self.repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(200, resp.status_code) + json_resp = json.loads(resp.content) + assert len(json_resp['success']) == 1 + assert len(json_resp['failed']) == 0 + + # share repo again will failed + data = { + 'operation': 'share', + 'share_type': 'group', + 'group_id': self.group_id, + 'repo_id': [self.repo_id, tmp_repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(200, resp.status_code) + json_resp = json.loads(resp.content) + assert len(json_resp['success']) == 1 + assert len(json_resp['failed']) == 1 + assert self.repo_id in json_resp['failed'][0]['repo_id'] + + self.remove_repo(tmp_repo_id) + + def test_share_with_invalid_operation(self): + self.login_as(self.user) + + data = { + 'operation': 'invalid_operation', + 'share_type': 'user', + 'username': self.admin_name, + 'repo_id': [self.repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(400, resp.status_code) + + data = { + 'operation': 'invalid_operation', + 'share_type': 'group', + 'group_id': self.group_id, + 'repo_id': [self.repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(400, resp.status_code) + + def test_share_with_invalid_share_type(self): + self.login_as(self.user) + + data = { + 'operation': 'share', + 'share_type': 'invalid_share_type', + 'username': self.admin_name, + 'repo_id': [self.repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(400, resp.status_code) + + data = { + 'operation': 'share', + 'share_type': 'invalid_share_type', + 'group_id': self.group_id, + 'repo_id': [self.repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(400, resp.status_code) + + def test_share_with_invalid_permisson(self): + self.login_as(self.user) + + data = { + 'operation': 'share', + 'share_type': 'user', + 'permission': 'invalid_permission', + 'username': self.admin_name, + 'repo_id': [self.repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(400, resp.status_code) + + data = { + 'operation': 'share', + 'share_type': 'group', + 'permission': 'invalid_permission', + 'group_id': self.group_id, + 'repo_id': [self.repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(400, resp.status_code) + + def test_share_with_invalid_user(self): + self.login_as(self.user) + + data = { + 'operation': 'share', + 'share_type': 'user', + 'username': 'invalid@user.com', + 'repo_id': [self.repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(404, resp.status_code) + + def test_share_with_not_exist_group(self): + self.login_as(self.user) + + data = { + 'operation': 'share', + 'share_type': 'group', + 'group_id': -1, + 'repo_id': [self.repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(404, resp.status_code) + + def test_share_with_not_group_member(self): + tmp_group_id = ccnet_api.create_group(randstring(10), self.admin_name) + + self.login_as(self.user) + + data = { + 'operation': 'share', + 'share_type': 'group', + 'group_id': tmp_group_id, + 'repo_id': [self.repo_id] + } + resp = self.client.post(self.url, data) + self.assertEqual(403, resp.status_code) From 610d60784640da7db163b97e3a8427de67286d9c Mon Sep 17 00:00:00 2001 From: lian Date: Sat, 13 May 2017 15:51:48 +0800 Subject: [PATCH 3/4] Revert "add create user avatar api" This reverts commit 3483783a282867dcafd06dbad5275157f69b7285. --- seahub/api2/urls.py | 2 +- seahub/api2/views.py | 50 ++++------------------------------------ tests/api/test_avatar.py | 19 ++++----------- 3 files changed, 10 insertions(+), 61 deletions(-) diff --git a/seahub/api2/urls.py b/seahub/api2/urls.py index 33284ac17c..fa09710578 100644 --- a/seahub/api2/urls.py +++ b/seahub/api2/urls.py @@ -90,7 +90,7 @@ urlpatterns = patterns('', url(r'^repo_history_changes/(?P[-0-9a-f]{36})/$', RepoHistoryChange.as_view()), url(r'^unseen_messages/$', UnseenMessagesCountView.as_view()), - url(r'^avatars/user/(?P\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/resized/(?P[0-9]+)/$', UserAvatarView.as_view(), name="api2-user-avatar"), + url(r'^avatars/user/(?P\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/resized/(?P[0-9]+)/$', UserAvatarView.as_view()), url(r'^avatars/group/(?P\d+)/resized/(?P[0-9]+)/$', GroupAvatarView.as_view()), url(r'^groups/$', Groups.as_view()), diff --git a/seahub/api2/views.py b/seahub/api2/views.py index bc2269157d..9ea7d0b1f7 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -42,13 +42,9 @@ from .utils import get_diff_details, \ from seahub.api2.base import APIView from seahub.api2.models import TokenV2, DESKTOP_PLATFORMS -from seahub.avatar.models import Avatar -from seahub.avatar.signals import avatar_updated from seahub.avatar.templatetags.avatar_tags import api_avatar_url, avatar -from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, grp_avatar -from seahub.avatar.settings import (AVATAR_MAX_AVATARS_PER_USER, - AVATAR_MAX_SIZE, AVATAR_ALLOWED_FILE_EXTS) - +from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, \ + grp_avatar from seahub.base.accounts import User from seahub.base.models import UserStarredFiles, DeviceToken from seahub.base.templatetags.seahub_tags import email2nickname, \ @@ -3848,9 +3844,9 @@ class GroupRepo(APIView): content_type=json_content_type) class UserAvatarView(APIView): - authentication_classes = (TokenAuthentication, SessionAuthentication) + authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) - throttle_classes = (UserRateThrottle,) + throttle_classes = (UserRateThrottle, ) def get(self, request, user, size, format=None): url, is_default, date_uploaded = api_avatar_url(user, int(size)) @@ -3860,44 +3856,6 @@ class UserAvatarView(APIView): "mtime": get_timestamp(date_uploaded) } return Response(ret) - def post(self, request, user, size, format=None): - - image_file = request.FILES.get('avatar', None) - if not image_file: - error_msg = 'avatar invalid.' - return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - - (root, ext) = os.path.splitext(image_file.name.lower()) - if AVATAR_ALLOWED_FILE_EXTS and ext not in AVATAR_ALLOWED_FILE_EXTS: - error_msg = _(u"%(ext)s is an invalid file extension. Authorized extensions are : %(valid_exts_list)s") % {'ext' : ext, 'valid_exts_list' : ", ".join(AVATAR_ALLOWED_FILE_EXTS)} - return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - - if image_file.size > AVATAR_MAX_SIZE: - error_msg = _(u"Your file is too big (%(size)s), the maximum allowed size is %(max_valid_size)s") % { 'size' : filesizeformat(image_file.size), 'max_valid_size' : filesizeformat(AVATAR_MAX_SIZE)} - return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - - username = request.user.username - count = Avatar.objects.filter(emailuser=username).count() - if AVATAR_MAX_AVATARS_PER_USER > 1 and count >= AVATAR_MAX_AVATARS_PER_USER: - error_msg = _(u"You already have %(nb_avatars)d avatars, and the maximum allowed is %(nb_max_avatars)d.") % { 'nb_avatars' : count, 'nb_max_avatars' : AVATAR_MAX_AVATARS_PER_USER} - return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - - avatar = Avatar( - emailuser = username, - primary = True, - ) - avatar.avatar.save(image_file.name, image_file) - avatar.save() - avatar_updated.send(sender=Avatar, user=request.user, avatar=avatar) - - url, is_default, date_uploaded = api_avatar_url(request.user, int(size)) - ret = { - "url": request.build_absolute_uri(url), - "mtime": get_timestamp(date_uploaded) - } - - return Response(ret) - class GroupAvatarView(APIView): authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) diff --git a/tests/api/test_avatar.py b/tests/api/test_avatar.py index c06e414179..4cda2996e5 100644 --- a/tests/api/test_avatar.py +++ b/tests/api/test_avatar.py @@ -1,27 +1,18 @@ -import os +import unittest + from tests.api.apitestbase import ApiTestBase from tests.api.urls import AVATAR_BASE_URL, GROUPS_URL -from tests.common.utils import randstring, urljoin +from tests.common.utils import randstring, apiurl, urljoin class AvatarApiTest(ApiTestBase): - - def test_get_user_avatar(self): + def test_user_avatar(self): avatar_url = urljoin(AVATAR_BASE_URL, 'user', self.username, '/resized/80/') info = self.get(avatar_url).json() self.assertIsNotNone(info['url']) self.assertIsNotNone(info['is_default']) self.assertIsNotNone(info['mtime']) - def test_create_user_avatar(self): - avatar_url = urljoin(AVATAR_BASE_URL, 'user', self.username, '/resized/80/') - avatar_file = os.path.join(os.getcwd(), 'media/img/seafile-logo.png') - - with open(avatar_file) as f: - json_resp = self.post(avatar_url, files={'avatar': f}).json() - - assert 'media/avatars' in json_resp['url'] - - def test_get_group_avatar(self): + def test_group_avatar(self): gname = randstring(16) data = {'group_name': gname} res = self.put(GROUPS_URL, data=data) From 0d4bdedb37c8109f2a51d0373e9caa02bd153397 Mon Sep 17 00:00:00 2001 From: lian Date: Sat, 13 May 2017 16:16:34 +0800 Subject: [PATCH 4/4] update user avatar --- seahub/api2/endpoints/user_avatar.py | 64 +++++++++++++++++++++++++ seahub/urls.py | 4 ++ tests/api/endpoints/test_user_avatar.py | 26 ++++++++++ 3 files changed, 94 insertions(+) create mode 100644 seahub/api2/endpoints/user_avatar.py create mode 100644 tests/api/endpoints/test_user_avatar.py diff --git a/seahub/api2/endpoints/user_avatar.py b/seahub/api2/endpoints/user_avatar.py new file mode 100644 index 0000000000..61092df02f --- /dev/null +++ b/seahub/api2/endpoints/user_avatar.py @@ -0,0 +1,64 @@ +# Copyright (c) 2012-2016 Seafile Ltd. +import os +import logging + +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework import status + +from django.utils.translation import ugettext as _ +from django.template.defaultfilters import filesizeformat + +from seahub.api2.authentication import TokenAuthentication +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.utils import api_error + +from seahub.avatar.models import Avatar +from seahub.avatar.signals import avatar_updated +from seahub.avatar.settings import (AVATAR_MAX_AVATARS_PER_USER, + AVATAR_MAX_SIZE, AVATAR_ALLOWED_FILE_EXTS) + +logger = logging.getLogger(__name__) + +class UserAvatarView(APIView): + authentication_classes = (TokenAuthentication,) + permission_classes = (IsAuthenticated,) + throttle_classes = (UserRateThrottle,) + + def post(self, request): + + image_file = request.FILES.get('avatar', None) + if not image_file: + error_msg = 'avatar invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + (root, ext) = os.path.splitext(image_file.name.lower()) + if AVATAR_ALLOWED_FILE_EXTS and ext not in AVATAR_ALLOWED_FILE_EXTS: + error_msg = _(u"%(ext)s is an invalid file extension. Authorized extensions are : %(valid_exts_list)s") % {'ext' : ext, 'valid_exts_list' : ", ".join(AVATAR_ALLOWED_FILE_EXTS)} + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if image_file.size > AVATAR_MAX_SIZE: + error_msg = _(u"Your file is too big (%(size)s), the maximum allowed size is %(max_valid_size)s") % { 'size' : filesizeformat(image_file.size), 'max_valid_size' : filesizeformat(AVATAR_MAX_SIZE)} + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + username = request.user.username + count = Avatar.objects.filter(emailuser=username).count() + if AVATAR_MAX_AVATARS_PER_USER > 1 and count >= AVATAR_MAX_AVATARS_PER_USER: + error_msg = _(u"You already have %(nb_avatars)d avatars, and the maximum allowed is %(nb_max_avatars)d.") % { 'nb_avatars' : count, 'nb_max_avatars' : AVATAR_MAX_AVATARS_PER_USER} + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + try: + avatar = Avatar( + emailuser = username, + primary = True, + ) + avatar.avatar.save(image_file.name, image_file) + avatar.save() + avatar_updated.send(sender=Avatar, user=request.user, avatar=avatar) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + return Response({'success': True}) diff --git a/seahub/urls.py b/seahub/urls.py index 79381320ec..7e396840ab 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -37,6 +37,7 @@ from seahub.api2.endpoints.share_link_zip_task import ShareLinkZipTaskView from seahub.api2.endpoints.query_zip_progress import QueryZipProgressView from seahub.api2.endpoints.invitations import InvitationsView from seahub.api2.endpoints.invitation import InvitationView +from seahub.api2.endpoints.user_avatar import UserAvatarView from seahub.api2.endpoints.admin.login import Login from seahub.api2.endpoints.admin.file_audit import FileAudit @@ -229,6 +230,9 @@ urlpatterns = patterns( url(r'^api/v2.1/invitations/$', InvitationsView.as_view()), url(r'^api/v2.1/invitations/(?P[a-f0-9]{32})/$', InvitationView.as_view()), + ## user::avatar + url(r'^api/v2.1/user-avatar/$', UserAvatarView.as_view(), name='api-v2.1-user-avatar'), + ## admin::sysinfo url(r'^api/v2.1/admin/sysinfo/$', SysInfo.as_view(), name='api-v2.1-sysinfo'), diff --git a/tests/api/endpoints/test_user_avatar.py b/tests/api/endpoints/test_user_avatar.py new file mode 100644 index 0000000000..1535a5bd09 --- /dev/null +++ b/tests/api/endpoints/test_user_avatar.py @@ -0,0 +1,26 @@ +import os +from tests.api.apitestbase import ApiTestBase +from tests.api.urls import AVATAR_BASE_URL +from tests.common.utils import urljoin +from tests.common.common import BASE_URL +from django.core.urlresolvers import reverse + +class AvatarApiTest(ApiTestBase): + + def test_create_user_avatar(self): + + # update user avatar + avatar_url = reverse('api-v2.1-user-avatar') + avatar_url = urljoin(BASE_URL, avatar_url) + avatar_file = os.path.join(os.getcwd(), 'media/img/seafile-logo.png') + + with open(avatar_file) as f: + json_resp = self.post(avatar_url, files={'avatar': f}).json() + + assert json_resp['success'] == True + + # assert is NOT default avatar + avatar_url = urljoin(AVATAR_BASE_URL, 'user', self.username, '/resized/80/') + info = self.get(avatar_url).json() + assert 'resized' in info['url'] + assert info['is_default'] == False