mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-18 00:00:00 +00:00
Merge pull request #1067 from haiwen/multi_institution
[inst] Add institution feature
This commit is contained in:
@@ -9,6 +9,7 @@ RequestContext.
|
||||
|
||||
import re
|
||||
|
||||
from django.conf import settings as dj_settings
|
||||
from constance import config
|
||||
|
||||
from seahub.settings import SEAFILE_VERSION, SITE_TITLE, SITE_NAME, \
|
||||
@@ -85,6 +86,7 @@ def base(request):
|
||||
'sysadmin_extra_enabled': ENABLE_SYSADMIN_EXTRA,
|
||||
'grps': grps,
|
||||
'multi_tenancy': MULTI_TENANCY,
|
||||
'multi_institution': getattr(dj_settings, 'MULTI_INSTITUTION', False),
|
||||
'search_repo_id': search_repo_id,
|
||||
'SITE_ROOT': SITE_ROOT,
|
||||
}
|
||||
|
0
seahub/institutions/__init__.py
Normal file
0
seahub/institutions/__init__.py
Normal file
3
seahub/institutions/admin.py
Normal file
3
seahub/institutions/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
27
seahub/institutions/decorators.py
Normal file
27
seahub/institutions/decorators.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from django.http import Http404
|
||||
|
||||
from seahub.profile.models import Profile
|
||||
|
||||
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:
|
||||
return func(request, *args, **kwargs)
|
||||
raise Http404
|
||||
return _decorated
|
||||
|
||||
def inst_admin_can_manage_user(func):
|
||||
"""
|
||||
Decorator for views check whether inst admin has permission to manage that
|
||||
user.
|
||||
"""
|
||||
def _decorated(request, *args, **kwargs):
|
||||
if request.user.inst_admin is True:
|
||||
email = kwargs['email']
|
||||
p = Profile.objects.get_profile_by_user(email)
|
||||
if p and p.institution == request.user.institution.name:
|
||||
return func(request, *args, **kwargs)
|
||||
raise Http404
|
||||
return _decorated
|
22
seahub/institutions/middleware.py
Normal file
22
seahub/institutions/middleware.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from django.conf import settings
|
||||
|
||||
from seahub.institutions.models import InstitutionAdmin
|
||||
|
||||
|
||||
class InstitutionMiddleware(object):
|
||||
def process_request(self, request):
|
||||
if not getattr(settings, 'MULTI_INSTITUTION', False):
|
||||
return None
|
||||
|
||||
username = request.user.username
|
||||
|
||||
# todo: record to session to avoid database query
|
||||
|
||||
try:
|
||||
inst_admin = InstitutionAdmin.objects.get(user=username)
|
||||
except InstitutionAdmin.DoesNotExist:
|
||||
return None
|
||||
|
||||
request.user.institution = inst_admin.institution
|
||||
request.user.inst_admin = True
|
||||
return None
|
30
seahub/institutions/migrations/0001_initial.py
Normal file
30
seahub/institutions/migrations/0001_initial.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Institution',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('create_time', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InstitutionAdmin',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('user', models.EmailField(max_length=254)),
|
||||
('institution', models.ForeignKey(to='institutions.Institution')),
|
||||
],
|
||||
),
|
||||
]
|
0
seahub/institutions/migrations/__init__.py
Normal file
0
seahub/institutions/migrations/__init__.py
Normal file
12
seahub/institutions/models.py
Normal file
12
seahub/institutions/models.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class Institution(models.Model):
|
||||
name = models.CharField(max_length=200)
|
||||
create_time = models.DateTimeField(default=timezone.now)
|
||||
|
||||
|
||||
class InstitutionAdmin(models.Model):
|
||||
institution = models.ForeignKey(Institution)
|
||||
user = models.EmailField()
|
15
seahub/institutions/templates/institutions/base.html
Normal file
15
seahub/institutions/templates/institutions/base.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block inst_admin %}<a href="{{ SITE_ROOT }}" title="{% trans "Exit" %}"><img src="{{ MEDIA_URL }}img/admin_out.png" alt="" /></a>{% endblock %}
|
||||
|
||||
{% block left_panel %}
|
||||
<div class="side-tabnav">
|
||||
<h3 class="hd">{{ request.user.institution.name }}</h3>
|
||||
<ul class="side-tabnav-tabs">
|
||||
<li class="tab {% block cur_users %}{% endblock %}">
|
||||
<a href="{% url "institutions:useradmin" %}"><span class="sf2-icon-user"></span>{% trans "Users" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
27
seahub/institutions/templates/institutions/info.html
Normal file
27
seahub/institutions/templates/institutions/info.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends "institutions/base.html" %}
|
||||
{% load seahub_tags i18n %}
|
||||
|
||||
{% block cur_info %}tab-cur{% endblock %}
|
||||
|
||||
{% block right_panel %}
|
||||
<h3 class="hd">{% trans "Info" %}</h3>
|
||||
|
||||
<dl>
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ inst.name }}</dd>
|
||||
|
||||
<dt>{% trans "Libraries" %}</dt>
|
||||
<dd>{{repos_count}}</dd>
|
||||
|
||||
<dt>{% trans "Active Users" %} / {% trans "Total Users" %}</dt>
|
||||
<dd>
|
||||
{% if active_users_count %}{{ active_users_count }}{% else %}--{% endif %}
|
||||
/
|
||||
{% if users_count %}{{ users_count }}{% else %}--{% endif %}
|
||||
</dd>
|
||||
|
||||
<dt>{% trans "Groups" %}</dt>
|
||||
<dd>{{groups_count}}</dd>
|
||||
</dl>
|
||||
{% endblock %}
|
||||
|
130
seahub/institutions/templates/institutions/user_info.html
Normal file
130
seahub/institutions/templates/institutions/user_info.html
Normal file
@@ -0,0 +1,130 @@
|
||||
{% extends "institutions/base.html" %}
|
||||
{% load i18n avatar_tags seahub_tags %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block extra_style %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static "css/select2-3.5.2.css" %}" />
|
||||
<link rel="stylesheet" type="text/css" href="{% static "css/select2.custom.css" %}" />
|
||||
<style type="text/css">
|
||||
#left-panel { position:relative; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block left_panel %}
|
||||
<a class="go-back" title="{% trans "Back to user list" %}" href="{% url 'institutions:useradmin' %}"><span class="icon-chevron-left"></span></a>
|
||||
<div class="side-info">
|
||||
<h3 class="hd">{% trans "Profile" %}</h3>
|
||||
{% avatar email 48 %}
|
||||
<dl>
|
||||
<dt>{% trans "Email" %}</dt>
|
||||
<dd>{{ email }}</dd>
|
||||
|
||||
{% if profile %}
|
||||
<dt>{% trans "Name" context "true name" %}</dt>
|
||||
<dd>{{ profile.nickname }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if d_profile %}
|
||||
<dt>{% trans "Department" %}</dt>
|
||||
<dd>{{ d_profile.department }}</dd>
|
||||
|
||||
<dt>{% trans "Telephone" %}</dt>
|
||||
<dd>{{ d_profile.telephone }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
<h3 class="hd">{% trans "Space Used" %}</h3>
|
||||
<p>{% trans "Used" %}: {{ space_usage|seahub_filesizeformat }} {% if space_quota > 0 %} / {{ space_quota|seahub_filesizeformat }} {% endif %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block right_panel %}
|
||||
<div id="tabs" class="tab-tabs">
|
||||
<div class="hd ovhd">
|
||||
<ul class="tab-tabs-nav fleft">
|
||||
<li class="tab"><a href="#owned" class="a">{% trans "Owned Libs" %}</a></li>
|
||||
<li class="tab"><a href="#user-admin-groups" class="a">{% trans "Groups" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="owned">
|
||||
{% if owned_repos %}
|
||||
<table class="repo-list">
|
||||
<tr>
|
||||
<th width="4%"><!--icon--></th>
|
||||
<th width="35%">{% trans "Name" %}</th>
|
||||
<th width="16%">{% trans "Size"%}</th>
|
||||
<th width="25%">{% trans "Last Update"%}</th>
|
||||
<th width="20%">{% trans "Operations" %}</th>
|
||||
</tr>
|
||||
|
||||
{% for repo in owned_repos %}
|
||||
<tr>
|
||||
{% if repo.encrypted %}
|
||||
<td><img src="{{MEDIA_URL}}img/sync-folder-encrypt-20.png" title="{% trans "Encrypted"%}" alt="{% trans "library icon" %}" /></td>
|
||||
{% else %}
|
||||
<td><img src="{{MEDIA_URL}}img/sync-folder-20.png?t=1387267140" title="{% trans "Read-Write" %}" alt="{% trans "library icon" %}" /></td>
|
||||
{% endif %}
|
||||
|
||||
{% if not repo.name %}
|
||||
<td>Broken ({{repo.id}})</td>
|
||||
{% else %}
|
||||
{% if repo.encrypted %}
|
||||
<td>{{ repo.name }}</td>
|
||||
{% elif enable_sys_admin_view_repo %}
|
||||
<td><a href="{% url 'sys_admin_repo' repo.id %}">{{ repo.name }}</a></td>
|
||||
{% else %}
|
||||
<td>{{ repo.name }}</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<td>{{ repo.size|filesizeformat }}</td>
|
||||
<td>{{ repo.last_modify|translate_seahub_time }}</td>
|
||||
<td data-id="{{ repo.props.id }}" data-name="{{repo.name}}">
|
||||
<div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="empty-tips">
|
||||
<h2 class="alc">{% trans "This user has not created any libraries" %}</h2>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="user-admin-groups">
|
||||
{% if personal_groups %}
|
||||
<table>
|
||||
<tr>
|
||||
<th width="30%">{% trans "Name" %}</th>
|
||||
<th width="30%">{% trans "Role" %}</th>
|
||||
<th width="25%">{% trans "Create At" %}</th>
|
||||
<th width="15%">{% trans "Operations" %}</th>
|
||||
</tr>
|
||||
{% for group in personal_groups %}
|
||||
<tr>
|
||||
<td><a href="{% url 'sys_admin_group_info' group.id %}">{{ group.group_name }}</a></td>
|
||||
<td>{{ group.role }}</td>
|
||||
<td>{{ group.timestamp|tsstr_sec }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="empty-tips">
|
||||
<h2 class="alc">{% trans "This user has not created or joined any groups" %}</h2>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "sysadmin/repo_transfer_form.html" %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block extra_script %}
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
{% endblock %}
|
66
seahub/institutions/templates/institutions/useradmin.html
Normal file
66
seahub/institutions/templates/institutions/useradmin.html
Normal file
@@ -0,0 +1,66 @@
|
||||
{% extends "institutions/base.html" %}
|
||||
{% load seahub_tags i18n %}
|
||||
{% block cur_users %}tab-cur{% endblock %}
|
||||
|
||||
|
||||
{% block right_panel %}
|
||||
<div class="tabnav ovhd">
|
||||
<ul class="tabnav-tabs fleft">
|
||||
<li class="tabnav-tab tabnav-tab-cur"><a href="{% url 'institutions:useradmin' %}">{% trans "Users" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
{% 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 %}
|
3
seahub/institutions/tests.py
Normal file
3
seahub/institutions/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
11
seahub/institutions/urls.py
Normal file
11
seahub/institutions/urls.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from .views import info, useradmin, user_info, user_remove
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url('^info/$', info, name="info"),
|
||||
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'),
|
||||
)
|
133
seahub/institutions/views.py
Normal file
133
seahub/institutions/views.py
Normal file
@@ -0,0 +1,133 @@
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template import RequestContext
|
||||
from django.utils.translation import ugettext as _
|
||||
import seaserv
|
||||
from seaserv import seafile_api
|
||||
from pysearpc import SearpcError
|
||||
|
||||
from seahub.base.accounts import User
|
||||
from seahub.base.decorators import require_POST
|
||||
from seahub.base.models import UserLastLogin
|
||||
from seahub.institutions.decorators import (inst_admin_required,
|
||||
inst_admin_can_manage_user)
|
||||
from seahub.profile.models import Profile, DetailedProfile
|
||||
from seahub.utils.rpc import mute_seafile_api
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def _populate_user_quota_usage(user):
|
||||
"""Populate space/share quota to user.
|
||||
|
||||
Arguments:
|
||||
- `user`:
|
||||
"""
|
||||
try:
|
||||
user.space_usage = seafile_api.get_user_self_usage(user.email)
|
||||
user.space_quota = seafile_api.get_user_quota(user.email)
|
||||
except SearpcError as e:
|
||||
logger.error(e)
|
||||
user.space_usage = -1
|
||||
user.space_quota = -1
|
||||
|
||||
@inst_admin_required
|
||||
def info(request):
|
||||
"""List instituion info.
|
||||
"""
|
||||
inst = request.user.institution
|
||||
|
||||
return render_to_response('institutions/info.html', {
|
||||
'inst': inst,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
@inst_admin_required
|
||||
def useradmin(request):
|
||||
"""List users in the institution.
|
||||
"""
|
||||
inst = request.user.institution
|
||||
usernames = [x.user for x in Profile.objects.filter(institution=inst.name)]
|
||||
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.html', {
|
||||
'inst': inst,
|
||||
'users': users,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
@inst_admin_required
|
||||
@inst_admin_can_manage_user
|
||||
def user_info(request, email):
|
||||
"""Show user info, libraries and groups.
|
||||
"""
|
||||
owned_repos = mute_seafile_api.get_owned_repo_list(email,
|
||||
ret_corrupted=True)
|
||||
in_repos = mute_seafile_api.get_share_in_repo_list(email, -1, -1)
|
||||
space_usage = mute_seafile_api.get_user_self_usage(email)
|
||||
space_quota = mute_seafile_api.get_user_quota(email)
|
||||
|
||||
# get user profile
|
||||
profile = Profile.objects.get_profile_by_user(email)
|
||||
d_profile = DetailedProfile.objects.get_detailed_profile_by_user(email)
|
||||
|
||||
try:
|
||||
personal_groups = seaserv.get_personal_groups_by_user(email)
|
||||
except SearpcError as e:
|
||||
logger.error(e)
|
||||
personal_groups = []
|
||||
|
||||
for g in personal_groups:
|
||||
try:
|
||||
is_group_staff = seaserv.check_group_staff(g.id, email)
|
||||
except SearpcError as e:
|
||||
logger.error(e)
|
||||
is_group_staff = False
|
||||
|
||||
if email == g.creator_name:
|
||||
g.role = _('Owner')
|
||||
elif is_group_staff:
|
||||
g.role = _('Admin')
|
||||
else:
|
||||
g.role = _('Member')
|
||||
|
||||
return render_to_response(
|
||||
'institutions/user_info.html', {
|
||||
'owned_repos': owned_repos,
|
||||
'space_quota': space_quota,
|
||||
'space_usage': space_usage,
|
||||
'in_repos': in_repos,
|
||||
'email': email,
|
||||
'profile': profile,
|
||||
'd_profile': d_profile,
|
||||
'personal_groups': personal_groups,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
@require_POST
|
||||
@inst_admin_required
|
||||
@inst_admin_can_manage_user
|
||||
def user_remove(request, email):
|
||||
"""Remove a institution user.
|
||||
"""
|
||||
referer = request.META.get('HTTP_REFERER', None)
|
||||
next = reverse('institutions:useradmin') if referer is None else referer
|
||||
|
||||
try:
|
||||
user = User.objects.get(email=email)
|
||||
user.delete()
|
||||
messages.success(request, _(u'Successfully deleted %s') % user.username)
|
||||
except User.DoesNotExist:
|
||||
messages.error(request, _(u'Failed to delete: the user does not exist'))
|
||||
|
||||
return HttpResponseRedirect(next)
|
@@ -199,6 +199,7 @@ INSTALLED_APPS = (
|
||||
'seahub.avatar',
|
||||
'seahub.base',
|
||||
'seahub.contacts',
|
||||
'seahub.institutions',
|
||||
'seahub.wiki',
|
||||
'seahub.group',
|
||||
'seahub.message',
|
||||
|
@@ -91,6 +91,11 @@
|
||||
{% block org_admin %}<a href="{% url 'org_user_admin'%}" title="{% trans "Admin" %}" class="a"><img src="{{ MEDIA_URL }}img/admin_in.png" alt="" /></a>{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if request.user.inst_admin %}
|
||||
<div class="manage">
|
||||
{% block inst_admin %}<a href="{% url "institutions:useradmin" %}" title="{% trans "Admin" %}" class="a"><img src="{{ MEDIA_URL }}img/admin_in.png" alt="" /></a>{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %} {# for non-logged-in user #}
|
||||
|
@@ -102,6 +102,12 @@
|
||||
{% block org_admin %}<a href="{% url 'org_user_admin'%}" title="{% trans "Admin" %}" class="a"><img src="{{ MEDIA_URL }}img/admin_in.png" alt="" /></a>{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if request.user.inst_admin %}
|
||||
<div class="manage">
|
||||
{% block inst_admin %}<a href="{% url "institutions:useradmin" %}" title="{% trans "Admin" %}" class="a"><img src="{{ MEDIA_URL }}img/admin_in.png" alt="" /></a>{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@@ -25,6 +25,11 @@
|
||||
<a href="{{ SITE_ROOT }}sys/orgadmin/"><span class="sf2-icon-organization"></span>{% trans "Organizations" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if multi_institution %}
|
||||
<li class="tab {% block cur_inst %}{% endblock %}">
|
||||
<a href="{{ SITE_ROOT }}sys/instadmin/"><span class="sf2-icon-organization"></span>{% trans "Institutions" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="tab {% block cur_notice %}{% endblock %}">
|
||||
<a href="{{ SITE_ROOT }}sys/notificationadmin/"><span class="sf2-icon-msgs"></span>{% trans "Notifications" %}</a>
|
||||
</li>
|
||||
|
71
seahub/templates/sysadmin/sys_inst_admin.html
Normal file
71
seahub/templates/sysadmin/sys_inst_admin.html
Normal file
@@ -0,0 +1,71 @@
|
||||
{% extends "sysadmin/base.html" %}
|
||||
{% load i18n seahub_tags %}
|
||||
|
||||
{% block cur_inst %}tab-cur{% endblock %}
|
||||
|
||||
{% block right_panel %}
|
||||
<div class="tabnav ovhd">
|
||||
<ul class="tabnav-tabs fleft">
|
||||
<h3>{% trans "All Institutions" %}</h3>
|
||||
</ul>
|
||||
<div class="fright">
|
||||
<button id="add-inst-btn">{% trans "Add" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="add-inst-form" action="" method="post" class="hide">{% csrf_token %}
|
||||
<h3>{% trans "Add institution" %}</h3>
|
||||
<label for="id_name">{% trans "Name" %}</label><br />
|
||||
<input type="text" name="name" id="id_name" class="input" /><br />
|
||||
<p class="error hide"></p>
|
||||
<button type="submit" class="submit">{% trans "Submit" %}</button>
|
||||
</form>
|
||||
|
||||
{% if insts %}
|
||||
<table>
|
||||
<tr>
|
||||
<th width="62%">{% trans "Name" %}</th>
|
||||
<th width="20%">{% trans "Create At" %}</th>
|
||||
<th width="18%">{% trans "Operations" %}</th>
|
||||
</tr>
|
||||
{% for inst in insts %}
|
||||
<tr>
|
||||
<td><a href="{% url "sys_inst_info_user" 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>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% include "snippets/admin_paginator.html" %}
|
||||
{% else %}
|
||||
<p>{% trans "Empty" %}</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
<script type="text/javascript">
|
||||
$('#add-inst-btn').click(function() {
|
||||
$('#add-inst-form').modal();
|
||||
$('#simplemodal-container').css({'width':'auto', 'height':'auto'});
|
||||
});
|
||||
$('#add-inst-form').submit(function() {
|
||||
var form = $(this),
|
||||
form_id = $(this).attr('id'),
|
||||
name = $.trim(form.find('[name="name"]').val());
|
||||
|
||||
if (!name) {
|
||||
apply_form_error(form_id, "{% trans "Name cannot be blank" %}");
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
addConfirmTo($('.rm-link'), {
|
||||
'title':"{% trans "Delete Institution" %}",
|
||||
'con':"{% trans "Are you sure you want to delete %s ?" %}",
|
||||
'post': true // post request
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
24
seahub/templates/sysadmin/sys_inst_info_base.html
Normal file
24
seahub/templates/sysadmin/sys_inst_info_base.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends "admin_base.html" %}
|
||||
{% load i18n seahub_tags %}
|
||||
|
||||
{% block nav_orgadmin_class %}class="cur"{% endblock %}
|
||||
|
||||
{% block extra_style %}
|
||||
<style type="text/css">
|
||||
#left-panel { position:relative; }
|
||||
#set-quota-form {
|
||||
min-width:255px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% 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>
|
||||
<dl>
|
||||
<dt>{% trans "Number of members" %}</dt>
|
||||
<dd>{{ users_count }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
120
seahub/templates/sysadmin/sys_inst_info_user.html
Normal file
120
seahub/templates/sysadmin/sys_inst_info_user.html
Normal file
@@ -0,0 +1,120 @@
|
||||
{% 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 tabnav-tab-cur"><a href="{% url 'sys_inst_info_user' inst.pk %}">{% trans "Members" %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<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 InstAdmin" %}{% else %}{% trans "Set InstAdmin" %}{% endif %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<div id="activate-msg" class="hide">
|
||||
<p>{% trans "Activating..., please wait" %}</p>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
<script type="text/javascript">
|
||||
|
||||
addConfirmTo($('.js-toggle-admin'), {
|
||||
'title':"{% trans "Toggle Admin" %}",
|
||||
'con':"{% trans "Sure ?" %}",
|
||||
'post': true
|
||||
});
|
||||
|
||||
$('tr:gt(0)').hover(
|
||||
function() {
|
||||
$(this).find('.user-status-edit-icon').removeClass('vh');
|
||||
},
|
||||
function() {
|
||||
$(this).find('.user-status-edit-icon').addClass('vh');
|
||||
}
|
||||
);
|
||||
$('.user-status-edit-icon').click(function() {
|
||||
$(this).parent().addClass('hide');
|
||||
$(this).parent().next().removeClass('hide'); // show 'select'
|
||||
});
|
||||
$('.user-status-select').change(function() {
|
||||
var select = $(this),
|
||||
select_val = select.val(),
|
||||
uid = select.parents('tr').attr('data-userid'),
|
||||
url = "{{ SITE_ROOT }}useradmin/toggle_status/" + uid + "/?s=" + select_val;
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
cache: false,
|
||||
beforeSend: function() {
|
||||
if (select_val == 1) {
|
||||
// show activating popup
|
||||
$('#activate-msg').modal();
|
||||
$('#simplemodal-container').css({'height':'auto'});
|
||||
}
|
||||
},
|
||||
success: function(data) {
|
||||
if (data['email_sent']) {
|
||||
feedback("{% trans "Edit succeeded, an email has been sent." %}", 'success');
|
||||
} else if (data['email_sent'] === false) {
|
||||
feedback("{% trans "Edit succeeded, but failed to send email, please check your email configuration." %}", 'success');
|
||||
} else {
|
||||
feedback("{% trans "Edit succeeded" %}", 'success');
|
||||
}
|
||||
select.prev().children('span').html(select.children('option[value="' +select.val() + '"]').text());
|
||||
select.addClass('hide');
|
||||
select.prev().removeClass('hide');
|
||||
$.modal.close();
|
||||
},
|
||||
error: function() {
|
||||
feedback("{% trans "Edit failed." %}", 'error');
|
||||
select.addClass('hide');
|
||||
select.prev().removeClass('hide');
|
||||
$.modal.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
$(document).click(function(e) {
|
||||
var target = e.target || event.srcElement;
|
||||
// target can't be edit-icon
|
||||
if (!$('.user-status-edit-icon, .user-status-select').is(target)) {
|
||||
$('.user-status').removeClass('hide');
|
||||
$('.user-status-select').addClass('hide');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@@ -201,6 +201,7 @@ urlpatterns = patterns(
|
||||
(r'^help/', include('seahub.help.urls')),
|
||||
url(r'^captcha/', include('captcha.urls')),
|
||||
(r'^thumbnail/', include('seahub.thumbnail.urls')),
|
||||
url(r'^inst/', include('seahub.institutions.urls', app_name='institutions', namespace='institutions')),
|
||||
|
||||
### system admin ###
|
||||
url(r'^sys/info/$', sys_info, name='sys_info'),
|
||||
@@ -234,6 +235,10 @@ urlpatterns = patterns(
|
||||
url(r'^sys/orgadmin/(?P<org_id>\d+)/group/$', sys_org_info_group, name='sys_org_info_group'),
|
||||
url(r'^sys/orgadmin/(?P<org_id>\d+)/library/$', sys_org_info_library, name='sys_org_info_library'),
|
||||
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+)/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'),
|
||||
url(r'^sys/uploadlink/remove/$', sys_upload_link_remove, name='sys_upload_link_remove'),
|
||||
|
@@ -34,7 +34,7 @@ from seahub.base.templatetags.seahub_tags import tsstr_sec, email2nickname, \
|
||||
from seahub.auth import authenticate
|
||||
from seahub.auth.decorators import login_required, login_required_ajax
|
||||
from seahub.constants import GUEST_USER, DEFAULT_USER
|
||||
|
||||
from seahub.institutions.models import Institution, InstitutionAdmin
|
||||
from seahub.utils import IS_EMAIL_CONFIGURED, string2list, is_valid_username, \
|
||||
is_pro_version, send_html_email, get_user_traffic_list, get_server_id, \
|
||||
clear_token, gen_file_get_url, is_org_context, handle_virus_record, \
|
||||
@@ -2244,3 +2244,129 @@ def sys_check_license(request):
|
||||
result['expiration_date'] = expiration
|
||||
|
||||
return HttpResponse(json.dumps(result), content_type=content_type)
|
||||
|
||||
@login_required
|
||||
@sys_staff_required
|
||||
def sys_inst_admin(request):
|
||||
"""List institutions.
|
||||
"""
|
||||
if request.method == "POST":
|
||||
inst_name = request.POST.get('name').strip()
|
||||
if not inst_name:
|
||||
messages.error(request, 'Name is required.')
|
||||
return HttpResponseRedirect(reverse('sys_inst_admin'))
|
||||
|
||||
Institution.objects.create(name=inst_name)
|
||||
messages.success(request, _('Success'))
|
||||
|
||||
return HttpResponseRedirect(reverse('sys_inst_admin'))
|
||||
|
||||
# Make sure page request is an int. If not, deliver first page.
|
||||
try:
|
||||
current_page = int(request.GET.get('page', '1'))
|
||||
per_page = int(request.GET.get('per_page', '100'))
|
||||
except ValueError:
|
||||
current_page = 1
|
||||
per_page = 100
|
||||
|
||||
offset = per_page * (current_page - 1)
|
||||
limit = per_page + 1
|
||||
insts = Institution.objects.all()[offset:offset + limit]
|
||||
|
||||
if len(insts) == per_page + 1:
|
||||
page_next = True
|
||||
else:
|
||||
page_next = False
|
||||
|
||||
return render_to_response(
|
||||
'sysadmin/sys_inst_admin.html', {
|
||||
'insts': insts,
|
||||
'current_page': current_page,
|
||||
'prev_page': current_page - 1,
|
||||
'next_page': current_page + 1,
|
||||
'per_page': per_page,
|
||||
'page_next': page_next,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
@login_required
|
||||
@sys_staff_required
|
||||
@require_POST
|
||||
def sys_inst_remove(request, inst_id):
|
||||
"""Delete an institution.
|
||||
"""
|
||||
try:
|
||||
inst = Institution.objects.get(pk=inst_id)
|
||||
except Institution.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
inst.delete()
|
||||
messages.success(request, _('Success'))
|
||||
|
||||
return HttpResponseRedirect(reverse('sys_inst_admin'))
|
||||
|
||||
@login_required
|
||||
@sys_staff_required
|
||||
def sys_inst_info_user(request, inst_id):
|
||||
"""List institution members.
|
||||
"""
|
||||
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)]
|
||||
usernames = [x.user for x in Profile.objects.filter(institution=inst.name)]
|
||||
users = [User.objects.get(x) for x in usernames]
|
||||
last_logins = UserLastLogin.objects.filter(username__in=[x.email 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 = len(users)
|
||||
|
||||
return render_to_response('sysadmin/sys_inst_info_user.html', {
|
||||
'inst': inst,
|
||||
'users': users,
|
||||
'users_count': users_count,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
@login_required
|
||||
@sys_staff_required
|
||||
@require_POST
|
||||
def sys_inst_toggle_admin(request, inst_id, email):
|
||||
"""Set or revoke an institution admin.
|
||||
"""
|
||||
try:
|
||||
inst = Institution.objects.get(pk=inst_id)
|
||||
except Institution.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
try:
|
||||
u = User.objects.get(email=email)
|
||||
except User.DoesNotExist:
|
||||
assert False, 'TODO'
|
||||
|
||||
if u.is_staff:
|
||||
assert False
|
||||
|
||||
res = InstitutionAdmin.objects.filter(institution=inst, user=email)
|
||||
if len(res) == 0:
|
||||
InstitutionAdmin.objects.create(institution=inst, user=email)
|
||||
elif len(res) == 1:
|
||||
res[0].delete()
|
||||
# todo: expire user's session
|
||||
else:
|
||||
assert False
|
||||
|
||||
messages.success(request, _('Success'))
|
||||
return HttpResponseRedirect(reverse('sys_inst_info_user', args=[inst.pk]))
|
||||
|
Reference in New Issue
Block a user