From 3fac2a79c2e2569c74c4b58d79d932e6f838ba3c Mon Sep 17 00:00:00 2001 From: zhengxie Date: Wed, 8 Jun 2016 15:39:12 +0800 Subject: [PATCH] [inst] Add search user and list admins --- seahub/institutions/decorators.py | 2 +- .../templates/institutions/base.html | 5 ++ .../institutions/useradmin_search.html | 73 +++++++++++++++ seahub/institutions/urls.py | 3 +- seahub/institutions/views.py | 31 +++++++ seahub/templates/sysadmin/sys_inst_admin.html | 2 +- .../sysadmin/sys_inst_info_admins.html | 62 +++++++++++++ .../sysadmin/sys_inst_info_base.html | 7 +- .../sysadmin/sys_inst_info_user.html | 3 +- .../sysadmin/sys_inst_search_user.html | 61 +++++++++++++ seahub/urls.py | 4 +- seahub/views/sysadmin.py | 88 ++++++++++++++++++- tests/seahub/institutions/test_views.py | 69 +++++++++++++++ .../sysadmin/test_sys_inst_info_admins.py | 34 +++++++ .../views/sysadmin/test_sys_inst_info_user.py | 29 ++++++ .../sysadmin/test_sys_inst_search_user.py | 31 +++++++ .../sysadmin/test_sys_inst_toggle_admin.py | 35 ++++++++ 17 files changed, 529 insertions(+), 10 deletions(-) create mode 100644 seahub/institutions/templates/institutions/useradmin_search.html create mode 100644 seahub/templates/sysadmin/sys_inst_info_admins.html create mode 100644 seahub/templates/sysadmin/sys_inst_search_user.html create mode 100644 tests/seahub/institutions/test_views.py create mode 100644 tests/seahub/views/sysadmin/test_sys_inst_info_admins.py create mode 100644 tests/seahub/views/sysadmin/test_sys_inst_info_user.py create mode 100644 tests/seahub/views/sysadmin/test_sys_inst_search_user.py create mode 100644 tests/seahub/views/sysadmin/test_sys_inst_toggle_admin.py diff --git a/seahub/institutions/decorators.py b/seahub/institutions/decorators.py index 6b1d9e7574..5be2405418 100644 --- a/seahub/institutions/decorators.py +++ b/seahub/institutions/decorators.py @@ -7,7 +7,7 @@ def inst_admin_required(func): Decorator for views check whether user is a institution admin. """ def _decorated(request, *args, **kwargs): - if request.user.inst_admin is True: + if request.user.is_authenticated() and request.user.inst_admin is True: return func(request, *args, **kwargs) raise Http404 return _decorated diff --git a/seahub/institutions/templates/institutions/base.html b/seahub/institutions/templates/institutions/base.html index aedb9bbf59..428e42a4e5 100644 --- a/seahub/institutions/templates/institutions/base.html +++ b/seahub/institutions/templates/institutions/base.html @@ -11,5 +11,10 @@ {% trans "Users" %} + +
+ +
+ {% endblock %} diff --git a/seahub/institutions/templates/institutions/useradmin_search.html b/seahub/institutions/templates/institutions/useradmin_search.html new file mode 100644 index 0000000000..b58038f51f --- /dev/null +++ b/seahub/institutions/templates/institutions/useradmin_search.html @@ -0,0 +1,73 @@ +{% extends "institutions/base.html" %} +{% load seahub_tags i18n %} +{% block cur_users %}tab-cur{% endblock %} + + +{% block right_panel %} +

{% trans "Search User"%}

+ +
+
+ +
+

{% trans "Result"%}

+ +{% if users %} + + + + + + + + + + {% for user in users %} + + + + + + + + {% endfor %} +
{% trans "Email" %}{% trans "Status" %}{% trans "Space Used" %}{% trans "Create At / Last Login" %}{% trans "Operations" %}
{{ user.email }} + +
+ {% if user.is_active %} + {% trans "Active" %} + {% else %} + {% trans "Inactive" %} + {% endif %} +
+
+

{{ user.space_usage|seahub_filesizeformat }} {% if user.space_quota > 0 %} / {{ user.space_quota|seahub_filesizeformat }} {% endif %}

+
+ {% if user.source == "DB" %} + {{ user.ctime|tsstr_sec }} /
+ {% else %} + -- / + {% endif %} + {% if user.last_login %}{{user.last_login|translate_seahub_time}} {% else %} -- {% endif %} +
+ {% if not user.is_self %} + {% trans "Delete" %} + {% endif %} +
+ +{% else %} +

{% trans "Empty" %}

+{% endif %} + +{% endblock %} + +{% block extra_script %} + +{% endblock %} diff --git a/seahub/institutions/urls.py b/seahub/institutions/urls.py index a0a85cd74b..fededaf9eb 100644 --- a/seahub/institutions/urls.py +++ b/seahub/institutions/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import patterns, url -from .views import info, useradmin, user_info, user_remove +from .views import info, useradmin, user_info, user_remove, useradmin_search urlpatterns = patterns( '', @@ -8,4 +8,5 @@ urlpatterns = patterns( url('^useradmin/$', useradmin, name="useradmin"), url(r'^useradmin/info/(?P[^/]+)/$', user_info, name='user_info'), url(r'^useradmin/remove/(?P[^/]+)/$', user_remove, name='user_remove'), + url('^useradmin/search/$', useradmin_search, name="useradmin_search"), ) diff --git a/seahub/institutions/views.py b/seahub/institutions/views.py index 0bbadf273c..b2f869c91b 100644 --- a/seahub/institutions/views.py +++ b/seahub/institutions/views.py @@ -86,6 +86,37 @@ def useradmin(request): 'page_next': page_next, }, context_instance=RequestContext(request)) +@inst_admin_required +def useradmin_search(request): + """Search users in the institution. + """ + inst = request.user.institution + + q = request.GET.get('q', '').lower() + if not q: + return HttpResponseRedirect(reverse('institutions:useradmin')) + + profiles = Profile.objects.filter(institution=inst.name) + usernames = [x.user for x in profiles if q in x.user] + users = [User.objects.get(x) for x in usernames] + + last_logins = UserLastLogin.objects.filter(username__in=[x.username for x in users]) + for u in users: + if u.username == request.user.username: + u.is_self = True + + _populate_user_quota_usage(u) + + for e in last_logins: + if e.username == u.username: + u.last_login = e.last_login + + return render_to_response('institutions/useradmin_search.html', { + 'inst': inst, + 'users': users, + 'q': q, + }, context_instance=RequestContext(request)) + @inst_admin_required @inst_admin_can_manage_user def user_info(request, email): diff --git a/seahub/templates/sysadmin/sys_inst_admin.html b/seahub/templates/sysadmin/sys_inst_admin.html index b5b7026b33..0df490bc49 100644 --- a/seahub/templates/sysadmin/sys_inst_admin.html +++ b/seahub/templates/sysadmin/sys_inst_admin.html @@ -30,7 +30,7 @@ {% for inst in insts %} - {{ inst.name }} + {{ inst.name }} {{ inst.create_time|translate_seahub_time }} {% trans "Remove" %} diff --git a/seahub/templates/sysadmin/sys_inst_info_admins.html b/seahub/templates/sysadmin/sys_inst_info_admins.html new file mode 100644 index 0000000000..d5c7bc4547 --- /dev/null +++ b/seahub/templates/sysadmin/sys_inst_info_admins.html @@ -0,0 +1,62 @@ +{% extends "sysadmin/sys_inst_info_base.html" %} +{% load i18n seahub_tags %} + +{% block right_panel %} + + +{% if admins %} + + + + + + + + + + {% for user in admins %} + + + + + + + + {% endfor %} +
{% trans "Email" %}{% trans "Status" %}{% trans "Space Used" %}{% trans "Create At / Last Login" %}{% trans "Operations" %}
{{ user.email }} +
+ {% if user.is_active %} + {% trans "Active" %} + {% else %} + {% trans "Inactive" %} + {% endif %} +
+
+

{{ user.space_usage|seahub_filesizeformat }} {% if user.space_quota > 0 %} / {{ user.space_quota|seahub_filesizeformat }} {% endif %}

+
+ {{ user.ctime|tsstr_sec }} / {% if user.last_login %}{{user.last_login|translate_seahub_time}} {% else %} -- {% endif %} + + {% trans "Revoke Admin" %} +
+{% else %} +

{% trans "Empty" %}

+{% endif %} + +{% endblock %} + +{% block extra_script %} + +{% endblock %} diff --git a/seahub/templates/sysadmin/sys_inst_info_base.html b/seahub/templates/sysadmin/sys_inst_info_base.html index 471b3db6ba..0200a06479 100644 --- a/seahub/templates/sysadmin/sys_inst_info_base.html +++ b/seahub/templates/sysadmin/sys_inst_info_base.html @@ -15,10 +15,15 @@ {% block left_panel %}
-

{{ inst.name }}

+

{{ inst.name }}

{% trans "Number of members" %}
{{ users_count }}
+ +
+ +
+
{% endblock %} diff --git a/seahub/templates/sysadmin/sys_inst_info_user.html b/seahub/templates/sysadmin/sys_inst_info_user.html index 4e8e246fc7..25d86c1ca6 100644 --- a/seahub/templates/sysadmin/sys_inst_info_user.html +++ b/seahub/templates/sysadmin/sys_inst_info_user.html @@ -4,7 +4,8 @@ {% block right_panel %} diff --git a/seahub/templates/sysadmin/sys_inst_search_user.html b/seahub/templates/sysadmin/sys_inst_search_user.html new file mode 100644 index 0000000000..1c84c423de --- /dev/null +++ b/seahub/templates/sysadmin/sys_inst_search_user.html @@ -0,0 +1,61 @@ +{% extends "sysadmin/sys_inst_info_base.html" %} +{% load i18n seahub_tags %} + +{% block right_panel %} +

{% trans "Search User"%}

+ +
+
+ +
+

{% trans "Result"%}

+ +{% if users %} + + + + + + + + + + {% for user in users %} + + + + + + + + {% endfor %} +
{% trans "Email" %}{% trans "Status" %}{% trans "Space Used" %}{% trans "Create At / Last Login" %}{% trans "Operations" %}
{{ user.email }} +
+ {% if user.is_active %} + {% trans "Active" %} + {% else %} + {% trans "Inactive" %} + {% endif %} +
+
+

{{ user.space_usage|seahub_filesizeformat }} {% if user.space_quota > 0 %} / {{ user.space_quota|seahub_filesizeformat }} {% endif %}

+
+ {{ user.ctime|tsstr_sec }} / {% if user.last_login %}{{user.last_login|translate_seahub_time}} {% else %} -- {% endif %} + + {% if user.inst_admin %}{% trans "Revoke Admin" %}{% else %}{% trans "Set Admin" %}{% endif %} +
+{% else %} +

{% trans "No result" %}

+{% endif %} + +{% endblock %} + +{% block extra_script %} + +{% endblock %} diff --git a/seahub/urls.py b/seahub/urls.py index 340ca5a316..5a06f4f0ef 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -246,7 +246,9 @@ urlpatterns = patterns( url(r'^sys/orgadmin/(?P\d+)/setting/$', sys_org_info_setting, name='sys_org_info_setting'), url(r'^sys/instadmin/$', sys_inst_admin, name='sys_inst_admin'), url(r'^sys/instadmin/(?P\d+)/remove/$', sys_inst_remove, name='sys_inst_remove'), - url(r'^sys/instadmin/(?P\d+)/user/$', sys_inst_info_user, name='sys_inst_info_user'), + url(r'^sys/instadmin/(?P\d+)/users/$', sys_inst_info_user, name='sys_inst_info_users'), + url(r'^sys/instadmin/(?P\d+)/users/search/$', sys_inst_search_user, name='sys_inst_search_user'), + url(r'^sys/instadmin/(?P\d+)/admins/$', sys_inst_info_admins, name='sys_inst_info_admins'), url(r'^sys/instadmin/(?P\d+)/toggleadmin/(?P[^/]+)/$', sys_inst_toggle_admin, name='sys_inst_toggle_admin'), url(r'^sys/publinkadmin/$', sys_publink_admin, name='sys_publink_admin'), url(r'^sys/publink/remove/$', sys_publink_remove, name='sys_publink_remove'), diff --git a/seahub/views/sysadmin.py b/seahub/views/sysadmin.py index d9004558d7..79228c3c14 100644 --- a/seahub/views/sysadmin.py +++ b/seahub/views/sysadmin.py @@ -2339,7 +2339,7 @@ def sys_inst_remove(request, inst_id): @login_required @sys_staff_required def sys_inst_info_user(request, inst_id): - """List institution members. + """List institution members including admins. """ try: inst = Institution.objects.get(pk=inst_id) @@ -2378,7 +2378,7 @@ def sys_inst_info_user(request, inst_id): if last_login.username == u.email: u.last_login = last_login.last_login - users_count = len(users) + users_count = Profile.objects.filter(institution=inst.name).count() return render_to_response('sysadmin/sys_inst_info_user.html', { 'inst': inst, @@ -2391,6 +2391,80 @@ def sys_inst_info_user(request, inst_id): 'page_next': page_next, }, context_instance=RequestContext(request)) +@login_required +@sys_staff_required +def sys_inst_search_user(request, inst_id): + """Search institution members. + """ + try: + inst = Institution.objects.get(pk=inst_id) + except Institution.DoesNotExist: + raise Http404 + + q = request.GET.get('q', '').lower() + if not q: + return HttpResponseRedirect(reverse('sys_inst_info_users', args=[inst_id])) + + profiles = Profile.objects.filter(institution=inst.name) + usernames = [x.user for x in profiles if q in x.user] + users = [User.objects.get(x) for x in usernames] + + inst_admins = [x.user for x in InstitutionAdmin.objects.filter(institution=inst)] + last_logins = UserLastLogin.objects.filter(username__in=[x for x in users]) + for u in users: + _populate_user_quota_usage(u) + + if u.username in inst_admins: + u.inst_admin = True + else: + u.inst_admin = False + + # populate user last login time + u.last_login = None + for last_login in last_logins: + if last_login.username == u.email: + u.last_login = last_login.last_login + + users_count = Profile.objects.filter(institution=inst.name).count() + + return render_to_response('sysadmin/sys_inst_search_user.html', { + 'q': q, + 'inst': inst, + 'users': users, + 'users_count': users_count, + }, context_instance=RequestContext(request)) + +@login_required +@sys_staff_required +def sys_inst_info_admins(request, inst_id): + """List institution admins. + """ + try: + inst = Institution.objects.get(pk=inst_id) + except Institution.DoesNotExist: + raise Http404 + + inst_admins = [x.user for x in InstitutionAdmin.objects.filter(institution=inst)] + admins = [User.objects.get(x) for x in inst_admins] + + last_logins = UserLastLogin.objects.filter(username__in=[x.email for x in admins]) + for u in admins: + _populate_user_quota_usage(u) + + # populate user last login time + u.last_login = None + for last_login in last_logins: + if last_login.username == u.email: + u.last_login = last_login.last_login + + users_count = Profile.objects.filter(institution=inst.name).count() + + return render_to_response('sysadmin/sys_inst_info_admins.html', { + 'inst': inst, + 'admins': admins, + 'users_count': users_count, + }, context_instance=RequestContext(request)) + @login_required @sys_staff_required @require_POST @@ -2402,13 +2476,19 @@ def sys_inst_toggle_admin(request, inst_id, email): except Institution.DoesNotExist: raise Http404 + next = request.META.get('HTTP_REFERER', None) + if not next: + next = reverse('sys_inst_info_users', args=[inst.pk]) + try: u = User.objects.get(email=email) except User.DoesNotExist: assert False, 'TODO' if u.is_staff: - assert False + messages.error( + request, 'Can not assign institutional administration roles to global administrators') + return HttpResponseRedirect(next) res = InstitutionAdmin.objects.filter(institution=inst, user=email) if len(res) == 0: @@ -2420,4 +2500,4 @@ def sys_inst_toggle_admin(request, inst_id, email): assert False messages.success(request, _('Success')) - return HttpResponseRedirect(reverse('sys_inst_info_user', args=[inst.pk])) + return HttpResponseRedirect(next) diff --git a/tests/seahub/institutions/test_views.py b/tests/seahub/institutions/test_views.py new file mode 100644 index 0000000000..e58a25cfdb --- /dev/null +++ b/tests/seahub/institutions/test_views.py @@ -0,0 +1,69 @@ +from django.conf import settings +from django.core.urlresolvers import reverse +from django.http.cookie import parse_cookie +from django.test import override_settings + +from seahub.institutions.models import Institution, InstitutionAdmin +from seahub.profile.models import Profile +from seahub.test_utils import BaseTestCase + +settings.MIDDLEWARE_CLASSES += ( + 'seahub.institutions.middleware.InstitutionMiddleware', +) + + +class InstTestBase(BaseTestCase): + def setUp(self): + self.inst = Institution.objects.create(name='inst_test') + + assert len(Profile.objects.all()) == 0 + p = Profile.objects.add_or_update(self.user.username, '') + p.institution = self.inst.name + p.save() + + p = Profile.objects.add_or_update(self.admin.username, '') + p.institution = self.inst.name + p.save() + assert len(Profile.objects.all()) == 2 + + InstitutionAdmin.objects.create(institution=self.inst, + user=self.user.username) + +class InfoTest(InstTestBase): + @override_settings( + MIDDLEWARE_CLASSES=settings.MIDDLEWARE_CLASSES, + MULTI_INSTITUTION=True + ) + def test_can_render(self): + self.login_as(self.user) + + resp = self.client.get(reverse('institutions:info')) + self.assertEqual(200, resp.status_code) + assert resp.context['inst'] == self.inst + + +class UseradminTest(InstTestBase): + @override_settings( + MIDDLEWARE_CLASSES=settings.MIDDLEWARE_CLASSES, + MULTI_INSTITUTION=True + ) + def test_can_list(self): + self.login_as(self.user) + resp = self.client.get(reverse('institutions:useradmin')) + self.assertEqual(200, resp.status_code) + assert resp.context['inst'] == self.inst + assert len(resp.context['users']) == 2 + + +class UseradminSearchTest(InstTestBase): + @override_settings( + MIDDLEWARE_CLASSES=settings.MIDDLEWARE_CLASSES, + MULTI_INSTITUTION=True + ) + def test_can_search(self): + self.login_as(self.user) + resp = self.client.get(reverse('institutions:useradmin_search') + '?q=@') + self.assertEqual(200, resp.status_code) + assert resp.context['inst'] == self.inst + assert len(resp.context['users']) == 2 + assert resp.context['q'] == '@' diff --git a/tests/seahub/views/sysadmin/test_sys_inst_info_admins.py b/tests/seahub/views/sysadmin/test_sys_inst_info_admins.py new file mode 100644 index 0000000000..ee109b2179 --- /dev/null +++ b/tests/seahub/views/sysadmin/test_sys_inst_info_admins.py @@ -0,0 +1,34 @@ +from django.core.urlresolvers import reverse + +from seahub.institutions.models import Institution, InstitutionAdmin +from seahub.profile.models import Profile +from seahub.test_utils import BaseTestCase + +class SysInstInfoAdminsTest(BaseTestCase): + def setUp(self): + self.login_as(self.admin) + + self.inst = Institution.objects.create(name='inst_test') + self.url = reverse('sys_inst_info_users', args=[self.inst.pk]) + + assert len(Profile.objects.all()) == 0 + p = Profile.objects.add_or_update(self.user.username, '') + p.institution = self.inst.name + p.save() + + p = Profile.objects.add_or_update(self.admin.username, '') + p.institution = self.inst.name + p.save() + assert len(Profile.objects.all()) == 2 + + InstitutionAdmin.objects.create(institution=self.inst, + user=self.user.username) + InstitutionAdmin.objects.create(institution=self.inst, + user=self.admin.username) + + def test_can_list(self): + resp = self.client.get(self.url) + + self.assertTemplateUsed('sysadmin/sys_inst_info_admins.html') + assert resp.context['inst'] == self.inst + assert len(resp.context['users']) == 2 diff --git a/tests/seahub/views/sysadmin/test_sys_inst_info_user.py b/tests/seahub/views/sysadmin/test_sys_inst_info_user.py new file mode 100644 index 0000000000..2260323185 --- /dev/null +++ b/tests/seahub/views/sysadmin/test_sys_inst_info_user.py @@ -0,0 +1,29 @@ +from django.core.urlresolvers import reverse + +from seahub.institutions.models import Institution +from seahub.profile.models import Profile +from seahub.test_utils import BaseTestCase + +class SysInstInfoUserTest(BaseTestCase): + def setUp(self): + self.login_as(self.admin) + + self.inst = Institution.objects.create(name='inst_test') + self.url = reverse('sys_inst_info_users', args=[self.inst.pk]) + + assert len(Profile.objects.all()) == 0 + p = Profile.objects.add_or_update(self.user.username, '') + p.institution = self.inst.name + p.save() + + p = Profile.objects.add_or_update(self.admin.username, '') + p.institution = self.inst.name + p.save() + assert len(Profile.objects.all()) == 2 + + def test_can_list(self): + resp = self.client.get(self.url) + + self.assertTemplateUsed('sysadmin/sys_inst_info_users.html') + assert resp.context['inst'] == self.inst + assert len(resp.context['users']) == 2 diff --git a/tests/seahub/views/sysadmin/test_sys_inst_search_user.py b/tests/seahub/views/sysadmin/test_sys_inst_search_user.py new file mode 100644 index 0000000000..47e4d5c111 --- /dev/null +++ b/tests/seahub/views/sysadmin/test_sys_inst_search_user.py @@ -0,0 +1,31 @@ +from django.core.urlresolvers import reverse + +from seahub.institutions.models import Institution +from seahub.profile.models import Profile +from seahub.test_utils import BaseTestCase + + +class SysInstSearchUser(BaseTestCase): + def setUp(self): + self.inst = Institution.objects.create(name='test_inst') + + assert len(Profile.objects.all()) == 0 + p = Profile.objects.add_or_update(self.user.username, '') + p.institution = self.inst.name + p.save() + + p = Profile.objects.add_or_update(self.admin.username, '') + p.institution = self.inst.name + p.save() + assert len(Profile.objects.all()) == 2 + + self.url = reverse('sys_inst_search_user', args=[self.inst.id]) + + def test_can_search(self): + self.login_as(self.admin) + + resp = self.client.get(self.url + '?q=@') + self.assertEqual(200, resp.status_code) + + assert len(resp.context['users']) == 2 + assert resp.context['q'] == '@' diff --git a/tests/seahub/views/sysadmin/test_sys_inst_toggle_admin.py b/tests/seahub/views/sysadmin/test_sys_inst_toggle_admin.py new file mode 100644 index 0000000000..01ae01800b --- /dev/null +++ b/tests/seahub/views/sysadmin/test_sys_inst_toggle_admin.py @@ -0,0 +1,35 @@ +from django.core.urlresolvers import reverse +from django.http.cookie import parse_cookie + +from seahub.institutions.models import Institution, InstitutionAdmin +from seahub.profile.models import Profile +from seahub.test_utils import BaseTestCase + +class SysInstInfoUserTest(BaseTestCase): + def setUp(self): + self.login_as(self.admin) + + self.inst = Institution.objects.create(name='inst_test') + + assert len(Profile.objects.all()) == 0 + p = Profile.objects.add_or_update(self.user.username, '') + p.institution = self.inst.name + p.save() + assert len(Profile.objects.all()) == 1 + + self.url = reverse('sys_inst_toggle_admin', args=[self.inst.pk, + self.user.username]) + + def test_can_set_and_revoke_admin(self): + assert len(InstitutionAdmin.objects.filter(institution=self.inst)) == 0 + resp = self.client.post(self.url) + self.assertEqual(302, resp.status_code) + assert 'Success' in parse_cookie(resp.cookies)['messages'] + + assert len(InstitutionAdmin.objects.filter(institution=self.inst)) == 1 + + resp = self.client.post(self.url) + self.assertEqual(302, resp.status_code) + assert 'Success' in parse_cookie(resp.cookies)['messages'] + + assert len(InstitutionAdmin.objects.filter(institution=self.inst)) == 0