diff --git a/media/js/base.js b/media/js/base.js index 338cf59c14..3a53c711ac 100644 --- a/media/js/base.js +++ b/media/js/base.js @@ -861,7 +861,7 @@ function userInputOPtionsForSelect2(user_search_url) { // for search. both name & email can be searched. // use ' '(space) to separate name & email "text": users[i].name + ' ' + users[i].email, - "avatar": users[i].avatar, + "avatar_url": users[i].avatar_url, "name": users[i].name }); } @@ -873,8 +873,8 @@ function userInputOPtionsForSelect2(user_search_url) { // format items shown in the drop-down menu formatResult: function(item) { - if (item.avatar) { - return item.avatar + '' + HTMLescape(item.name) + '
' + HTMLescape(item.id) + '
'; + if (item.avatar_url) { + return '' + '' + HTMLescape(item.name) + '
' + HTMLescape(item.id) + '
'; } else { return; // if no match, show nothing } diff --git a/seahub/api2/endpoints/search_user.py b/seahub/api2/endpoints/search_user.py new file mode 100644 index 0000000000..a8bcdc01c4 --- /dev/null +++ b/seahub/api2/endpoints/search_user.py @@ -0,0 +1,165 @@ +import json + +from django.db.models import Q +from django.http import HttpResponse + +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.views import APIView +from rest_framework import status + +import seaserv + +from seahub.api2.authentication import TokenAuthentication +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.utils import api_error + +from seahub.utils import is_org_context +from seahub.base.accounts import User +from seahub.base.templatetags.seahub_tags import email2nickname +from seahub.profile.models import Profile +from seahub.contacts.models import Contact +from seahub.avatar.templatetags.avatar_tags import api_avatar_url +from seahub.settings import ENABLE_GLOBAL_ADDRESSBOOK, ENABLE_SEARCH_FROM_LDAP_DIRECTLY + + +class SearchUser(APIView): + """ Search user from contacts/all users + """ + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated,) + throttle_classes = (UserRateThrottle, ) + + def _can_use_global_address_book(self, request): + + return request.user.permissions.can_use_global_address_book() + + def get(self, request, format=None): + + + if not self._can_use_global_address_book(request): + return api_error(status.HTTP_403_FORBIDDEN, + 'Guest user can not use global address book.') + + q = request.GET.get('q', None) + + if not q: + return api_error(status.HTTP_400_BAD_REQUEST, 'Argument missing.') + + users_from_ccnet = [] + users_from_profile = [] + users_result = [] + username = request.user.username + + if request.cloud_mode: + if is_org_context(request): + url_prefix = request.user.org.url_prefix + users = seaserv.get_org_users_by_url_prefix(url_prefix, -1, -1) + + # search user from ccnet + users_from_ccnet = filter(lambda u: q in u.email, users) + + # when search profile, only search users in org + # 'nickname__contains' for search by nickname + # 'contact_email__contains' for search by contact email + users_from_profile = Profile.objects.filter(Q(user__in=[u.email for u in users]) & \ + (Q(nickname__contains=q)) | \ + Q(contact_email__contains=q)).values('user') + elif ENABLE_GLOBAL_ADDRESSBOOK: + users_from_ccnet = search_user_from_ccnet(q) + users_from_profile = Profile.objects.filter(Q(contact_email__contains=q) | \ + Q(nickname__contains=q)).values('user') + else: + # TODO delete this ? + users = [] + contacts = Contact.objects.get_contacts_by_user(username) + for c in contacts: + try: + user = User.objects.get(email = c.contact_email) + c.is_active = user.is_active + except User.DoesNotExist: + continue + + c.email = c.contact_email + users.append(c) + + users_from_ccnet = filter(lambda u: q in u.email, users) + # 'user__in' for only get profile of contacts + # 'nickname__contains' for search by nickname + # 'contact_email__contains' for search by contact + users_from_profile = Profile.objects.filter(Q(user__in=[u.email for u in users]) & \ + (Q(nickname__contains=q)) | \ + Q(contact_email__contains=q)).values('user') + else: + users_from_ccnet = search_user_from_ccnet(q) + users_from_profile = Profile.objects.filter(Q(contact_email__contains=q) | \ + Q(nickname__contains=q)).values('user') + + # remove inactive users and add to result + for u in users_from_ccnet[:10]: + if u.is_active: + users_result.append(u.email) + + for p in users_from_profile[:10]: + try: + user = User.objects.get(email = p['user']) + except User.DoesNotExist: + continue + + if not user.is_active: + continue + + users_result.append(p['user']) + + # remove duplicate emails + users_result = {}.fromkeys(users_result).keys() + + try: + include_self = int(request.GET.get('include_self', 1)) + except ValueError: + include_self = 1 + + if include_self == 0 and username in users_result: + # reomve myself + users_result.remove(username) + + try: + size = int(request.GET.get('avatar_size', 32)) + except ValueError: + size = 32 + + formated_result = format_searched_user_result(request, users_result, size)[:10] + return HttpResponse(json.dumps({"users": formated_result}), status=200, + content_type='application/json; charset=utf-8') + +def format_searched_user_result(request, users, size): + results = [] + + for email in users: + url, is_default, date_uploaded = api_avatar_url(email, size) + results.append({ + "email": email, + "avatar_url": request.build_absolute_uri(url), + "name": email2nickname(email), + "contact_email": Profile.objects.get_contact_email_by_user(email), + }) + + return results + +def search_user_from_ccnet(q): + users = [] + + db_users = seaserv.ccnet_threaded_rpc.search_emailusers('DB', q, 0, 10) + users.extend(db_users) + + count = len(users) + if count < 10: + ldap_imported_users = seaserv.ccnet_threaded_rpc.search_emailusers('LDAP', q, 0, 10 - count) + users.extend(ldap_imported_users) + + count = len(users) + if count < 10 and ENABLE_SEARCH_FROM_LDAP_DIRECTLY: + all_ldap_users = seaserv.ccnet_threaded_rpc.search_ldapusers(q, 0, 10 - count) + users.extend(all_ldap_users) + + return users diff --git a/seahub/api2/urls.py b/seahub/api2/urls.py index 036b8fa977..404527364d 100644 --- a/seahub/api2/urls.py +++ b/seahub/api2/urls.py @@ -7,6 +7,7 @@ from .endpoints.dir_shared_items import DirSharedItemsEndpoint 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 urlpatterns = patterns('', url(r'^ping/$', Ping.as_view()), diff --git a/seahub/api2/views.py b/seahub/api2/views.py index bfc172a80f..035cbaaca2 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -415,6 +415,7 @@ def get_searched_users(q): return searched_users + class Search(APIView): """ Search all the repos """ diff --git a/seahub/settings.py b/seahub/settings.py index 958025b574..a5258486a7 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -227,6 +227,9 @@ ACCOUNT_ACTIVATION_DAYS = 7 # allow seafile amdin view user's repo ENABLE_SYS_ADMIN_VIEW_REPO = False +#allow search from LDAP directly during auto-completion (not only search imported users) +ENABLE_SEARCH_FROM_LDAP_DIRECTLY = False + # show traffic on the UI SHOW_TRAFFIC = True diff --git a/static/scripts/common.js b/static/scripts/common.js index eb12128a8c..eb248b963b 100644 --- a/static/scripts/common.js +++ b/static/scripts/common.js @@ -564,7 +564,7 @@ define([ // for search. both name & email can be searched. // use ' '(space) to separate name & email "text": users[i].name + ' ' + users[i].email, - "avatar": users[i].avatar, + "avatar_url": users[i].avatar_url, "name": users[i].name }); } @@ -577,8 +577,8 @@ define([ // format items shown in the drop-down menu formatResult: function(item) { - if (item.avatar) { - return item.avatar + '' + _this.HTMLescape(item.name) + '
' + _this.HTMLescape(item.id) + '
'; + if (item.avatar_url) { + return '' + _this.HTMLescape(item.name) + '
' + _this.HTMLescape(item.id) + '
'; } else { return; // if no match, show nothing } diff --git a/tests/api/test_search_user.py b/tests/api/test_search_user.py index a419b2073c..3318b2dc07 100644 --- a/tests/api/test_search_user.py +++ b/tests/api/test_search_user.py @@ -12,20 +12,55 @@ class SearchUserTest(BaseTestCase): self.endpoint = reverse('search-user') def test_can_search(self): - p = Profile.objects.add_or_update(self.user.email, nickname="test") - p.contact_email = 'new_mail@test.com' + email = self.admin.email + nickname = 'admin_test' + contact_email= 'new_admin_test@test.com' + p = Profile.objects.add_or_update(email, nickname=nickname) + p.contact_email = contact_email p.save() - resp = self.client.get(self.endpoint + '?q=' + self.user.email) + resp = self.client.get(self.endpoint + '?q=' + email) json_resp = json.loads(resp.content) self.assertEqual(200, resp.status_code) assert json_resp['users'] is not None - assert json_resp['users'][0]['email'] == self.user.email - assert json_resp['users'][0]['avatar'] is not None + assert json_resp['users'][0]['email'] == email assert json_resp['users'][0]['avatar_url'] is not None - assert json_resp['users'][0]['name'] == 'test' - assert json_resp['users'][0]['contact_email'] == 'new_mail@test.com' + assert json_resp['users'][0]['name'] == nickname + assert json_resp['users'][0]['contact_email'] == contact_email + + def test_search_myself(self): + email = self.user.email + nickname = 'user_test' + contact_email= 'new_user_test@test.com' + p = Profile.objects.add_or_update(email, nickname=nickname) + p.contact_email = contact_email + p.save() + + resp = self.client.get(self.endpoint + '?q=' + email) + json_resp = json.loads(resp.content) + + self.assertEqual(200, resp.status_code) + assert json_resp['users'] is not None + assert json_resp['users'][0]['email'] == email + assert json_resp['users'][0]['avatar_url'] is not None + assert json_resp['users'][0]['name'] == nickname + assert json_resp['users'][0]['contact_email'] == contact_email + + def test_search_without_myself(self): + email = self.user.email + resp = self.client.get(self.endpoint + '?include_self=0&q=' + email) + json_resp = json.loads(resp.content) + + self.assertEqual(200, resp.status_code) + assert len(json_resp['users']) == 0 + + def test_search_unregistered_user(self): + resp = self.client.get(self.endpoint + '?q=unregistered_user@seafile.com') + json_resp = json.loads(resp.content) + + self.assertEqual(200, resp.status_code) + assert len(json_resp['users']) == 0 def test_can_search_by_nickname(self): admin_email = self.admin.email @@ -42,7 +77,6 @@ class SearchUserTest(BaseTestCase): self.assertEqual(200, resp.status_code) assert json_resp['users'] is not None assert json_resp['users'][0]['email'] == admin_email - assert json_resp['users'][0]['avatar'] is not None assert json_resp['users'][0]['avatar_url'] is not None assert json_resp['users'][0]['name'] == 'Carl Smith' assert json_resp['users'][0]['contact_email'] == 'new_mail@test.com' @@ -63,7 +97,6 @@ class SearchUserTest(BaseTestCase): self.assertEqual(200, resp.status_code) assert json_resp['users'] is not None assert json_resp['users'][0]['email'] == admin_email - assert json_resp['users'][0]['avatar'] is not None assert json_resp['users'][0]['avatar_url'] is not None assert json_resp['users'][0]['name'] == 'Carl Smith' assert json_resp['users'][0]['contact_email'] == 'new_mail@test.com' @@ -75,7 +108,6 @@ class SearchUserTest(BaseTestCase): self.assertEqual(200, resp.status_code) assert json_resp['users'] is not None assert json_resp['users'][0]['email'] == admin_email - assert json_resp['users'][0]['avatar'] is not None assert json_resp['users'][0]['avatar_url'] is not None assert json_resp['users'][0]['name'] == 'Carl Smith' assert json_resp['users'][0]['contact_email'] == 'new_mail@test.com'