diff --git a/seahub/base/context_processors.py b/seahub/base/context_processors.py index 78fb53ef68..562125d9a9 100644 --- a/seahub/base/context_processors.py +++ b/seahub/base/context_processors.py @@ -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, } diff --git a/seahub/institutions/__init__.py b/seahub/institutions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/seahub/institutions/admin.py b/seahub/institutions/admin.py new file mode 100644 index 0000000000..8c38f3f3da --- /dev/null +++ b/seahub/institutions/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/seahub/institutions/decorators.py b/seahub/institutions/decorators.py new file mode 100644 index 0000000000..6b1d9e7574 --- /dev/null +++ b/seahub/institutions/decorators.py @@ -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 diff --git a/seahub/institutions/middleware.py b/seahub/institutions/middleware.py new file mode 100644 index 0000000000..ca75c6ddeb --- /dev/null +++ b/seahub/institutions/middleware.py @@ -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 diff --git a/seahub/institutions/migrations/0001_initial.py b/seahub/institutions/migrations/0001_initial.py new file mode 100644 index 0000000000..f4e846a877 --- /dev/null +++ b/seahub/institutions/migrations/0001_initial.py @@ -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')), + ], + ), + ] diff --git a/seahub/institutions/migrations/__init__.py b/seahub/institutions/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/seahub/institutions/models.py b/seahub/institutions/models.py new file mode 100644 index 0000000000..6b4288f0c1 --- /dev/null +++ b/seahub/institutions/models.py @@ -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() diff --git a/seahub/institutions/templates/institutions/base.html b/seahub/institutions/templates/institutions/base.html new file mode 100644 index 0000000000..aedb9bbf59 --- /dev/null +++ b/seahub/institutions/templates/institutions/base.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block inst_admin %}{% endblock %} + +{% block left_panel %} +
+

{{ request.user.institution.name }}

+ +
+{% endblock %} diff --git a/seahub/institutions/templates/institutions/info.html b/seahub/institutions/templates/institutions/info.html new file mode 100644 index 0000000000..a2ae259f38 --- /dev/null +++ b/seahub/institutions/templates/institutions/info.html @@ -0,0 +1,27 @@ +{% extends "institutions/base.html" %} +{% load seahub_tags i18n %} + +{% block cur_info %}tab-cur{% endblock %} + +{% block right_panel %} +

{% trans "Info" %}

+ +
+
{% trans "Name" %}
+
{{ inst.name }}
+ +
{% trans "Libraries" %}
+
{{repos_count}}
+ +
{% trans "Active Users" %} / {% trans "Total Users" %}
+
+ {% if active_users_count %}{{ active_users_count }}{% else %}--{% endif %} + / + {% if users_count %}{{ users_count }}{% else %}--{% endif %} +
+ +
{% trans "Groups" %}
+
{{groups_count}}
+
+{% endblock %} + diff --git a/seahub/institutions/templates/institutions/user_info.html b/seahub/institutions/templates/institutions/user_info.html new file mode 100644 index 0000000000..8efba60a64 --- /dev/null +++ b/seahub/institutions/templates/institutions/user_info.html @@ -0,0 +1,130 @@ +{% extends "institutions/base.html" %} +{% load i18n avatar_tags seahub_tags %} +{% load staticfiles %} + +{% block extra_style %} + + + +{% endblock %} + +{% block left_panel %} + +
+

{% trans "Profile" %}

+ {% avatar email 48 %} +
+
{% trans "Email" %}
+
{{ email }}
+ + {% if profile %} +
{% trans "Name" context "true name" %}
+
{{ profile.nickname }}
+ {% endif %} + + {% if d_profile %} +
{% trans "Department" %}
+
{{ d_profile.department }}
+ +
{% trans "Telephone" %}
+
{{ d_profile.telephone }}
+ {% endif %} +
+ +

{% trans "Space Used" %}

+

{% trans "Used" %}: {{ space_usage|seahub_filesizeformat }} {% if space_quota > 0 %} / {{ space_quota|seahub_filesizeformat }} {% endif %}

+
+{% endblock %} + +{% block right_panel %} +
+
+ +
+ +
+ {% if owned_repos %} + + + + + + + + + + {% for repo in owned_repos %} + + {% if repo.encrypted %} + + {% else %} + + {% endif %} + + {% if not repo.name %} + + {% else %} + {% if repo.encrypted %} + + {% elif enable_sys_admin_view_repo %} + + {% else %} + + {% endif %} + {% endif %} + + + + + + {% endfor %} +
{% trans "Name" %}{% trans "Size"%}{% trans "Last Update"%}{% trans "Operations" %}
{% trans {% trans Broken ({{repo.id}}){{ repo.name }}{{ repo.name }}{{ repo.name }}{{ repo.size|filesizeformat }}{{ repo.last_modify|translate_seahub_time }} +
+
+
+ {% else %} +
+

{% trans "This user has not created any libraries" %}

+
+ {% endif %} +
+ +
+ {% if personal_groups %} + + + + + + + + {% for group in personal_groups %} + + + + + + + {% endfor %} +
{% trans "Name" %}{% trans "Role" %}{% trans "Create At" %}{% trans "Operations" %}
{{ group.group_name }}{{ group.role }}{{ group.timestamp|tsstr_sec }}
+ {% else %} +
+

{% trans "This user has not created or joined any groups" %}

+
+ {% endif %} +
+
+ +{% include "sysadmin/repo_transfer_form.html" %} +{% endblock %} + + +{% block extra_script %} + +{% endblock %} diff --git a/seahub/institutions/templates/institutions/useradmin.html b/seahub/institutions/templates/institutions/useradmin.html new file mode 100644 index 0000000000..20dd812927 --- /dev/null +++ b/seahub/institutions/templates/institutions/useradmin.html @@ -0,0 +1,66 @@ +{% extends "institutions/base.html" %} +{% load seahub_tags i18n %} +{% block cur_users %}tab-cur{% endblock %} + + +{% block right_panel %} +
+ +
+ + + + + + + + + + + {% 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 %} +
+ +{% endblock %} + +{% block extra_script %} + +{% endblock %} diff --git a/seahub/institutions/tests.py b/seahub/institutions/tests.py new file mode 100644 index 0000000000..7ce503c2dd --- /dev/null +++ b/seahub/institutions/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/seahub/institutions/urls.py b/seahub/institutions/urls.py new file mode 100644 index 0000000000..a0a85cd74b --- /dev/null +++ b/seahub/institutions/urls.py @@ -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[^/]+)/$', user_info, name='user_info'), + url(r'^useradmin/remove/(?P[^/]+)/$', user_remove, name='user_remove'), +) diff --git a/seahub/institutions/views.py b/seahub/institutions/views.py new file mode 100644 index 0000000000..25d62907e7 --- /dev/null +++ b/seahub/institutions/views.py @@ -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) diff --git a/seahub/settings.py b/seahub/settings.py index 2f17665ee2..1a414d886d 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -199,6 +199,7 @@ INSTALLED_APPS = ( 'seahub.avatar', 'seahub.base', 'seahub.contacts', + 'seahub.institutions', 'seahub.wiki', 'seahub.group', 'seahub.message', diff --git a/seahub/templates/base.html b/seahub/templates/base.html index dda8306b83..f5f61c4f91 100644 --- a/seahub/templates/base.html +++ b/seahub/templates/base.html @@ -91,6 +91,11 @@ {% block org_admin %}{% endblock %} {% endif %} + {% if request.user.inst_admin %} +
+ {% block inst_admin %}{% endblock %} +
+ {% endif %} {% else %} {# for non-logged-in user #} diff --git a/seahub/templates/base_for_backbone.html b/seahub/templates/base_for_backbone.html index 110f2fc872..115c1e2fc0 100644 --- a/seahub/templates/base_for_backbone.html +++ b/seahub/templates/base_for_backbone.html @@ -102,6 +102,12 @@ {% block org_admin %}{% endblock %} {% endif %} + {% if request.user.inst_admin %} +
+ {% block inst_admin %}{% endblock %} +
+ {% endif %} + {% endif %} diff --git a/seahub/templates/sysadmin/base.html b/seahub/templates/sysadmin/base.html index ab400e9439..47b95b6b89 100644 --- a/seahub/templates/sysadmin/base.html +++ b/seahub/templates/sysadmin/base.html @@ -25,6 +25,11 @@ {% trans "Organizations" %} {% endif %} + {% if multi_institution %} +
  • + {% trans "Institutions" %} +
  • + {% endif %}
  • {% trans "Notifications" %}
  • diff --git a/seahub/templates/sysadmin/sys_inst_admin.html b/seahub/templates/sysadmin/sys_inst_admin.html new file mode 100644 index 0000000000..2b4bcc9a6f --- /dev/null +++ b/seahub/templates/sysadmin/sys_inst_admin.html @@ -0,0 +1,71 @@ +{% extends "sysadmin/base.html" %} +{% load i18n seahub_tags %} + +{% block cur_inst %}tab-cur{% endblock %} + +{% block right_panel %} +
    +
      +

      {% trans "All Institutions" %}

      +
    +
    + +
    +
    + +
    {% csrf_token %} +

    {% trans "Add institution" %}

    +
    +
    +

    + +
    + +{% if insts %} + + + + + + + {% for inst in insts %} + + + + + + {% endfor %} +
    {% trans "Name" %}{% trans "Create At" %}{% trans "Operations" %}
    {{ inst.name }}{{ inst.create_time|translate_seahub_time }} + {% trans "Remove" %} +
    + +{% include "snippets/admin_paginator.html" %} +{% 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 new file mode 100644 index 0000000000..471b3db6ba --- /dev/null +++ b/seahub/templates/sysadmin/sys_inst_info_base.html @@ -0,0 +1,24 @@ +{% extends "admin_base.html" %} +{% load i18n seahub_tags %} + +{% block nav_orgadmin_class %}class="cur"{% endblock %} + +{% block extra_style %} + +{% endblock %} + +{% block left_panel %} + +
    +

    {{ 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 new file mode 100644 index 0000000000..001cc1107b --- /dev/null +++ b/seahub/templates/sysadmin/sys_inst_info_user.html @@ -0,0 +1,120 @@ +{% extends "sysadmin/sys_inst_info_base.html" %} +{% load i18n seahub_tags %} + +{% block right_panel %} + + + + + + + + + + + + {% 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 InstAdmin" %}{% else %}{% trans "Set InstAdmin" %}{% endif %} +
    + +
    +

    {% trans "Activating..., please wait" %}

    +
    + +{% endblock %} + +{% block extra_script %} + +{% endblock %} diff --git a/seahub/urls.py b/seahub/urls.py index 85cd34aec5..c9f37b569b 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -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\d+)/group/$', sys_org_info_group, name='sys_org_info_group'), url(r'^sys/orgadmin/(?P\d+)/library/$', sys_org_info_library, name='sys_org_info_library'), 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+)/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'), url(r'^sys/uploadlink/remove/$', sys_upload_link_remove, name='sys_upload_link_remove'), diff --git a/seahub/views/sysadmin.py b/seahub/views/sysadmin.py index 109db0bc18..6db1e3a8f3 100644 --- a/seahub/views/sysadmin.py +++ b/seahub/views/sysadmin.py @@ -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]))