1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-07-12 06:29:23 +00:00

[inst] Add search user and list admins

This commit is contained in:
zhengxie 2016-06-08 15:39:12 +08:00
parent 390c04ec99
commit 3fac2a79c2
No known key found for this signature in database
GPG Key ID: 652D4F38038852F0
17 changed files with 529 additions and 10 deletions

View File

@ -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

View File

@ -11,5 +11,10 @@
<a href="{% url "institutions:useradmin" %}"><span class="sf2-icon-user"></span>{% trans "Users" %}</a>
</li>
</ul>
<form action="{% url 'institutions:useradmin_search' %}" method="get" class="side-search-form">
<input type="text" name="q" class="input" value="{{q}}" placeholder="{% trans "Search users..." %}" />
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,73 @@
{% extends "institutions/base.html" %}
{% load seahub_tags i18n %}
{% block cur_users %}tab-cur{% endblock %}
{% block right_panel %}
<h3>{% trans "Search User"%}</h3>
<form id="search-user-form" method="get" action=".">
<label>{% trans "Email" %}</label><input type="text" name="q" class="input" value="{{q}}" /><br />
<input type="submit" value="{% trans "Submit" %}" class="submit" />
</form>
<h3>{% trans "Result"%}</h3>
{% if users %}
<table>
<tr>
<th width="36%">{% trans "Email" %}</th>
<th width="12%">{% trans "Status" %}</th>
<th width="16%">{% trans "Space Used" %}</th>
<th width="22%">{% trans "Create At / Last Login" %}</th>
<th width="14%">{% trans "Operations" %}</th>
</tr>
{% for user in users %}
<tr data-userid="{{user.email}}">
<td><a href="{% url 'institutions:user_info' user.email %}">{{ user.email }}</a>
</td>
<td>
<div class="user-status">
{% if user.is_active %}
<span class="user-status-cur-value">{% trans "Active" %}</span>
{% else %}
<span class="user-status-cur-value">{% trans "Inactive" %}</span>
{% endif %}
</div>
</td>
<td style="font-size:11px;">
<p> {{ user.space_usage|seahub_filesizeformat }} {% if user.space_quota > 0 %} / {{ user.space_quota|seahub_filesizeformat }} {% endif %} </p>
</td>
<td>
{% if user.source == "DB" %}
{{ user.ctime|tsstr_sec }} /<br />
{% else %}
-- /
{% endif %}
{% if user.last_login %}{{user.last_login|translate_seahub_time}} {% else %} -- {% endif %}
</td>
<td>
{% if not user.is_self %}
<a href="#" class="remove-user-btn op vh" data-url="{% url 'institutions:user_remove' user.email %}" data-target="{{ user.email }}">{% trans "Delete" %}</a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% else %}
<p>{% trans "Empty" %}</p>
{% endif %}
{% endblock %}
{% block extra_script %}
<script type="text/javascript">
addConfirmTo($('.remove-user-btn'), {
'title':"{% trans "Delete User" %}",
'con':"{% trans "Are you sure you want to delete %s ?" %}",
'post': true // post request
});
</script>
{% endblock %}

View File

@ -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<email>[^/]+)/$', user_info, name='user_info'),
url(r'^useradmin/remove/(?P<email>[^/]+)/$', user_remove, name='user_remove'),
url('^useradmin/search/$', useradmin_search, name="useradmin_search"),
)

View File

@ -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):

View File

@ -30,7 +30,7 @@
</tr>
{% for inst in insts %}
<tr>
<td><a href="{% url "sys_inst_info_user" inst.pk %}">{{ inst.name }}</a></td>
<td><a href="{% url "sys_inst_info_users" inst.pk %}">{{ inst.name }}</a></td>
<td>{{ inst.create_time|translate_seahub_time }} </td>
<td>
<a class="op vh rm-link" data-target="{{ inst.name }}" data-url="{% url "sys_inst_remove" inst.pk %}" href="#">{% trans "Remove" %}</a>

View File

@ -0,0 +1,62 @@
{% extends "sysadmin/sys_inst_info_base.html" %}
{% load i18n seahub_tags %}
{% block right_panel %}
<div class="tabnav">
<ul class="tabnav-tabs">
<li class="tabnav-tab"><a href="{% url 'sys_inst_info_users' inst.pk %}">{% trans "Members" %}</a></li>
<li class="tabnav-tab tabnav-tab-cur"><a href="{% url 'sys_inst_info_admins' inst.pk %}">{% trans "Admins" %}</a></li>
</ul>
</div>
{% if admins %}
<table>
<tr>
<th width="25%">{% trans "Email" %}</th>
<th width="10%">{% trans "Status" %}</th>
<th width="20%">{% trans "Space Used" %}</th>
<th width="25%">{% trans "Create At / Last Login" %}</th>
<th width="20%">{% trans "Operations" %}</th>
</tr>
{% for user in admins %}
<tr data-userid="{{user.email}}">
<td><a href="{% url 'user_info' user.email %}">{{ user.email }}</a></td>
<td>
<div class="user-status">
{% if user.is_active %}
<span class="user-status-cur-value">{% trans "Active" %}</span>
{% else %}
<span class="user-status-cur-value">{% trans "Inactive" %}</span>
{% endif %}
</div>
</td>
<td style="font-size:11px;">
<p> {{ user.space_usage|seahub_filesizeformat }} {% if user.space_quota > 0 %} / {{ user.space_quota|seahub_filesizeformat }} {% endif %} </p>
</td>
<td style="font-size:11px;">
{{ user.ctime|tsstr_sec }} / {% if user.last_login %}{{user.last_login|translate_seahub_time}} {% else %} -- {% endif %}
</td>
<td>
<a href="#" class="js-toggle-admin op vh" data-url="{% url 'sys_inst_toggle_admin' inst.pk user.email %}" data-target="{{ user.email }}">{% trans "Revoke Admin" %}</a>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<p>{% trans "Empty" %}</p>
{% endif %}
{% endblock %}
{% block extra_script %}
<script type="text/javascript">
addConfirmTo($('.js-toggle-admin'), {
'title': "Toggle Admin",
'con': "Sure ?",
'post': true
});
</script>
{% endblock %}

View File

@ -15,10 +15,15 @@
{% block left_panel %}
<a class="go-back" title="{% trans "Back" %}" href="{% url 'sys_inst_admin' %}"><span class="icon-chevron-left"></span></a>
<div class="side-info">
<h3 class="hd">{{ inst.name }}</h3>
<h3 class="hd"><a href="{% url 'sys_inst_info_users' inst.pk %}">{{ inst.name }}</a></h3>
<dl>
<dt>{% trans "Number of members" %}</dt>
<dd>{{ users_count }}</dd>
</dl>
<form action="{% url 'sys_inst_search_user' inst.pk %}" method="get" class="side-search-form">
<input type="text" name="q" class="input" value="{{q}}" placeholder="{% trans "Search users..." %}" />
</form>
</div>
{% endblock %}

View File

@ -4,7 +4,8 @@
{% block right_panel %}
<div class="tabnav">
<ul class="tabnav-tabs">
<li class="tabnav-tab tabnav-tab-cur"><a href="{% url 'sys_inst_info_user' inst.pk %}">{% trans "Members" %}</a></li>
<li class="tabnav-tab tabnav-tab-cur"><a href="{% url 'sys_inst_info_users' inst.pk %}">{% trans "Members" %}</a></li>
<li class="tabnav-tab"><a href="{% url 'sys_inst_info_admins' inst.pk %}">{% trans "Admins" %}</a></li>
</ul>
</div>

View File

@ -0,0 +1,61 @@
{% extends "sysadmin/sys_inst_info_base.html" %}
{% load i18n seahub_tags %}
{% block right_panel %}
<h3>{% trans "Search User"%}</h3>
<form id="search-user-form" method="get" action=".">
<label>{% trans "Email" %}</label><input type="text" name="q" class="input" value="{{q}}" /><br />
<input type="submit" value="{% trans "Submit" %}" class="submit" />
</form>
<h3>{% trans "Result"%}</h3>
{% if users %}
<table>
<tr>
<th width="25%">{% trans "Email" %}</th>
<th width="10%">{% trans "Status" %}</th>
<th width="20%">{% trans "Space Used" %}</th>
<th width="25%">{% trans "Create At / Last Login" %}</th>
<th width="20%">{% trans "Operations" %}</th>
</tr>
{% for user in users %}
<tr data-userid="{{user.email}}">
<td><a href="{% url 'user_info' user.email %}">{{ user.email }}</a></td>
<td>
<div class="user-status">
{% if user.is_active %}
<span class="user-status-cur-value">{% trans "Active" %}</span>
{% else %}
<span class="user-status-cur-value">{% trans "Inactive" %}</span>
{% endif %}
</div>
</td>
<td style="font-size:11px;">
<p> {{ user.space_usage|seahub_filesizeformat }} {% if user.space_quota > 0 %} / {{ user.space_quota|seahub_filesizeformat }} {% endif %} </p>
</td>
<td style="font-size:11px;">
{{ user.ctime|tsstr_sec }} / {% if user.last_login %}{{user.last_login|translate_seahub_time}} {% else %} -- {% endif %}
</td>
<td>
<a href="#" class="js-toggle-admin op vh" data-url="{% url 'sys_inst_toggle_admin' inst.pk user.email %}" data-target="{{ user.email }}">{% if user.inst_admin %}{% trans "Revoke Admin" %}{% else %}{% trans "Set Admin" %}{% endif %}</a>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<p>{% trans "No result" %}</p>
{% endif %}
{% endblock %}
{% block extra_script %}
<script type="text/javascript">
addConfirmTo($('.js-toggle-admin'), {
'title': "Toggle Admin",
'con': "Sure ?",
'post': true
});
</script>
{% endblock %}

View File

@ -246,7 +246,9 @@ urlpatterns = patterns(
url(r'^sys/orgadmin/(?P<org_id>\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<inst_id>\d+)/remove/$', sys_inst_remove, name='sys_inst_remove'),
url(r'^sys/instadmin/(?P<inst_id>\d+)/user/$', sys_inst_info_user, name='sys_inst_info_user'),
url(r'^sys/instadmin/(?P<inst_id>\d+)/users/$', sys_inst_info_user, name='sys_inst_info_users'),
url(r'^sys/instadmin/(?P<inst_id>\d+)/users/search/$', sys_inst_search_user, name='sys_inst_search_user'),
url(r'^sys/instadmin/(?P<inst_id>\d+)/admins/$', sys_inst_info_admins, name='sys_inst_info_admins'),
url(r'^sys/instadmin/(?P<inst_id>\d+)/toggleadmin/(?P<email>[^/]+)/$', 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'),

View File

@ -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)

View File

@ -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'] == '@'

View File

@ -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

View File

@ -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

View File

@ -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'] == '@'

View File

@ -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