1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-06 01:12:03 +00:00

Merge pull request #1457 from haiwen/admin-group

udpate admin groups page
This commit is contained in:
xiez
2017-02-09 18:11:09 +08:00
committed by GitHub
26 changed files with 1263 additions and 144 deletions

View File

@@ -0,0 +1,107 @@
# Copyright (c) 2012-2016 Seafile Ltd.
import logging
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
import seaserv
from seaserv import seafile_api, ccnet_api
from seahub.utils import is_org_context
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
logger = logging.getLogger(__name__)
def get_group_repo_info(repo):
result = {}
result['repo_id'] = repo.repo_id
result['name'] = repo.repo_name
result['size'] = repo.size
result['shared_by'] = repo.user
result['permission'] = repo.permission
result['group_id'] = repo.group_id
result['encrypted'] = repo.encrypted
return result
class AdminGroupLibraries(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
permission_classes = (IsAdminUser,)
def get(self, request, group_id, format=None):
""" List all group repos
Permission checking:
1. only admin can perform this action.
"""
group_id = int(group_id)
group = ccnet_api.get_group(group_id)
if not group:
error_msg = 'Group %d not found.' % group_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if is_org_context(request):
org_id = request.user.org.org_id
repos = seafile_api.get_org_group_repos(org_id, group_id)
else:
repos = seafile_api.get_repos_by_group(group_id)
group_repos_info = []
for repo in repos:
repo_info = get_group_repo_info(repo)
group_repos_info.append(repo_info)
group_libraries = {
'group_id': group_id,
'group_name': group.group_name,
'libraries': group_repos_info
}
return Response(group_libraries)
class AdminGroupLibrary(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
permission_classes = (IsAdminUser,)
def delete(self, request, group_id, repo_id, format=None):
""" Unshare repo from group
Permission checking:
1. only admin can perform this action.
"""
repo = seafile_api.get_repo(repo_id)
if not repo:
error_msg = 'Library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
group_id = int(group_id)
group = ccnet_api.get_group(group_id)
if not group:
error_msg = 'Group %d not found.' % group_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
try:
if is_org_context(request):
org_id = request.user.org.org_id
seaserv.del_org_group_repo(repo_id, org_id, group_id)
else:
repo_owner = seafile_api.get_repo_owner(repo_id)
seafile_api.unset_group_repo(repo_id, group_id, repo_owner)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({'success': True})

View File

@@ -0,0 +1,223 @@
# Copyright (c) 2012-2016 Seafile Ltd.
import logging
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from seaserv import seafile_api, ccnet_api
from seahub.group.utils import get_group_member_info, is_group_member
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
from seahub.base.accounts import User
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
logger = logging.getLogger(__name__)
class AdminGroupMembers(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
permission_classes = (IsAdminUser,)
def get(self, request, group_id, format=None):
""" List all group members
Permission checking:
1. only admin can perform this action.
"""
group_id = int(group_id)
group = ccnet_api.get_group(group_id)
if not group:
error_msg = 'Group %d not found.' % group_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
try:
avatar_size = int(request.GET.get('avatar_size',
AVATAR_DEFAULT_SIZE))
except ValueError:
avatar_size = AVATAR_DEFAULT_SIZE
try:
members = ccnet_api.get_group_members(group_id)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
group_members_info = []
for m in members:
member_info = get_group_member_info(request, group_id, m.user_name, avatar_size)
group_members_info.append(member_info)
group_members = {
'group_id': group_id,
'group_name': group.group_name,
'members': group_members_info
}
return Response(group_members)
def post(self, request, group_id):
"""
Bulk add group members.
Permission checking:
1. only admin can perform this action.
"""
# argument check
group_id = int(group_id)
group = ccnet_api.get_group(group_id)
if not group:
error_msg = 'Group %d not found.' % group_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
emails = request.POST.getlist('email', '')
if not emails:
error_msg = 'Email invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
result = {}
result['failed'] = []
result['success'] = []
emails_need_add = []
for email in emails:
try:
User.objects.get(email=email)
except User.DoesNotExist:
result['failed'].append({
'email': email,
'error_msg': 'User %s not found.' % email
})
continue
if ccnet_api.is_group_user(group_id, email):
result['failed'].append({
'email': email,
'error_msg': 'User %s is already a group member.' % email
})
continue
emails_need_add.append(email)
# Add user to group.
for email in emails_need_add:
try:
ccnet_api.group_add_member(group_id, group.creator_name, email)
member_info = get_group_member_info(request, group_id, email)
result['success'].append(member_info)
except Exception as e:
logger.error(e)
result['failed'].append({
'email': email,
'error_msg': 'Internal Server Error'
})
return Response(result)
class AdminGroupMember(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
permission_classes = (IsAdminUser,)
def put(self, request, group_id, email, format=None):
""" update role of a group member
Permission checking:
1. only admin can perform this action.
"""
# argument check
group_id = int(group_id)
group = ccnet_api.get_group(group_id)
if not group:
error_msg = 'Group %d not found.' % group_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
try:
User.objects.get(email=email)
except User.DoesNotExist:
error_msg = 'User %s not found.' % email
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
try:
if not is_group_member(group_id, email):
error_msg = 'Email %s invalid.' % email
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
is_admin = request.data.get('is_admin', '')
try:
# set/unset a specific group member as admin
if is_admin.lower() == 'true':
ccnet_api.group_set_admin(group_id, email)
elif is_admin.lower() == 'false':
ccnet_api.group_unset_admin(group_id, email)
else:
error_msg = 'is_admin invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
member_info = get_group_member_info(request, group_id, email)
return Response(member_info)
def delete(self, request, group_id, email, format=None):
""" Delete an user from group
Permission checking:
1. only admin can perform this action.
"""
# argument check
group_id = int(group_id)
group = ccnet_api.get_group(group_id)
if not group:
error_msg = 'Group %d not found.' % group_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
try:
User.objects.get(email=email)
except User.DoesNotExist:
error_msg = 'User %s not found.' % email
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# delete member from group
try:
if not is_group_member(group_id, email):
return Response({'success': True})
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
if group.creator_name == email:
error_msg = '%s is group owner, can not be removed.' % email
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
ccnet_api.group_remove_member(group_id, group.creator_name, email)
# remove repo-group share info of all 'email' owned repos
seafile_api.remove_group_repos_by_owner(group_id, email)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({'success': True})

View File

@@ -50,7 +50,7 @@ class GroupMembers(APIView):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
members = seaserv.get_group_members(group_id)
members = ccnet_api.get_group_members(group_id)
except SearpcError as e:
logger.error(e)
@@ -169,13 +169,12 @@ class GroupMember(APIView):
# set/unset a specific group member as admin
if is_admin.lower() == 'true':
seaserv.ccnet_threaded_rpc.group_set_admin(group_id, email)
ccnet_api.group_set_admin(group_id, email)
elif is_admin.lower() == 'false':
seaserv.ccnet_threaded_rpc.group_unset_admin(group_id, email)
ccnet_api.group_unset_admin(group_id, email)
else:
error_msg = 'is_admin invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
except SearpcError as e:
logger.error(e)
error_msg = 'Internal Server Error'
@@ -204,7 +203,7 @@ class GroupMember(APIView):
# user leave group
if username == email:
try:
seaserv.ccnet_threaded_rpc.quit_group(group_id, username)
ccnet_api.quit_group(group_id, username)
# remove repo-group share info of all 'email' owned repos
seafile_api.remove_group_repos_by_owner(group_id, email)
return Response({'success': True})
@@ -217,14 +216,14 @@ class GroupMember(APIView):
try:
if is_group_owner(group_id, username):
# group owner can delete all group member
seaserv.ccnet_threaded_rpc.group_remove_member(group_id, username, email)
ccnet_api.group_remove_member(group_id, username, email)
seafile_api.remove_group_repos_by_owner(group_id, email)
return Response({'success': True})
elif is_group_admin(group_id, username):
# group admin can NOT delete group owner/admin
if not is_group_admin_or_owner(group_id, email):
seaserv.ccnet_threaded_rpc.group_remove_member(group_id, username, email)
ccnet_api.group_remove_member(group_id, username, email)
seafile_api.remove_group_repos_by_owner(group_id, email)
return Response({'success': True})
else:

View File

@@ -4,6 +4,7 @@ import re
import logging
import seaserv
from seaserv import ccnet_api
from seahub.utils import is_org_context
from seahub.profile.models import Profile
@@ -44,7 +45,7 @@ def check_group_name_conflict(request, new_group_name):
if request.cloud_mode:
checked_groups = seaserv.get_personal_groups_by_user(username)
else:
checked_groups = seaserv.ccnet_threaded_rpc.get_all_groups(-1, -1)
checked_groups = ccnet_api.get_all_groups(-1, -1)
for g in checked_groups:
if g.group_name == new_group_name:
@@ -53,13 +54,13 @@ def check_group_name_conflict(request, new_group_name):
return False
def is_group_member(group_id, email):
return seaserv.is_group_user(group_id, email)
return ccnet_api.is_group_user(int(group_id), email)
def is_group_admin(group_id, email):
return seaserv.check_group_staff(group_id, email)
return ccnet_api.check_group_staff(int(group_id), email)
def is_group_owner(group_id, email):
group = seaserv.get_group(group_id)
group = ccnet_api.get_group(int(group_id))
if email == group.creator_name:
return True
else:
@@ -85,14 +86,23 @@ def get_group_member_info(request, group_id, email, avatar_size=AVATAR_DEFAULT_S
logger.error(e)
avatar_url = get_default_avatar_url()
is_admin = seaserv.check_group_staff(group_id, email)
role = 'Member'
group = ccnet_api.get_group(int(group_id))
is_admin = ccnet_api.check_group_staff(int(group_id), email)
if email == group.creator_name:
role = 'Owner'
elif is_admin:
role = 'Admin'
member_info = {
'group_id': group_id,
"name": email2nickname(email),
'email': email,
"contact_email": Profile.objects.get_contact_email_by_user(email),
"login_id": login_id,
"avatar_url": request.build_absolute_uri(avatar_url),
"is_admin": is_admin,
"role": role,
}
return member_info

View File

@@ -119,7 +119,7 @@
</tr>
{% for group in personal_groups %}
<tr>
<td><a href="{% url 'sys_admin_group_info' group.id %}">{{ group.group_name }}</a></td>
<td><a href="{{ SITE_ROOT }}sysadmin/#groups/{{ group.id }}/libs/">{{ group.group_name }}</a></td>
<td>{{ group.role }}</td>
<td>{{ group.timestamp|tsstr_sec }}</td>
<td></td>

View File

@@ -598,7 +598,7 @@
</script>
<script type="text/template" id="group-item-tmpl">
<td><a href="{{ SITE_ROOT }}sys/groupadmin/<%= id %>/"><%- name %></a></td>
<td><a href="{{ SITE_ROOT }}sysadmin/#groups/<%= id %>/libs/"><%- name %></a></td>
<td><a href="{{ SITE_ROOT }}useradmin/info/<% print(encodeURIComponent(owner)); %>/"><%- owner %></a></td>
<td><time title="<%= time %>"><%= time_from_now %></time></td>
<td>
@@ -623,7 +623,7 @@
</td>
<% } else { %>
<td>
<a href="{{ SITE_ROOT }}sys/groupadmin/<%= group_id %>/" target="_blank"><%- group_name %></a>
<a href="{{ SITE_ROOT }}sysadmin/#groups/<%= group_id %>/libs/" target="_blank"><%- group_name %></a>
</td>
<% } %>
<td>
@@ -718,3 +718,123 @@
</div>
</div>
</script>
<script type="text/template" id="groups-tabnav-tmpl">
<p class="path-bar">
<a class="normal" href="#groups/">{% trans "Groups" %}</a>
<span class="path-split">/</span>
</p>
<div class="tabnav ovhd">
<ul class="tabnav-tabs fleft">
<li class="tabnav-tab<% if (cur_tab == 'libs') { %> tabnav-tab-cur<% } %>">
<a href="#groups/<%= group_id %>/libs/">{% trans "Libraries" %}</a>
</li>
<li class="tabnav-tab<% if (cur_tab == 'members') { %> tabnav-tab-cur<% } %>">
<a href="#groups/<%= group_id %>/members/">{% trans "Members" %}</a>
</li>
</ul>
<% if (cur_tab == 'members') { %>
<div class="fright">
<button id="js-add-group-member">{% trans "Add Member" %}</button>
</div>
<% } %>
</div>
</script>
<script type="text/template" id="add-group-member-form-tmpl">
<form id="add-group-member-form" action="" method="post" class="hide">{% csrf_token %}
<h3 id="dialogTitle">{% trans "Add Member" %}</h3>
<label for="email">{% trans "Email" %}</label><br />
<input type="text" name="email" value="" id="email" /><br />
<p class="error hide"></p>
<input type="submit" class="submit" value="{% trans "Submit" %}" />
</form>
</script>
<script type="text/template" id="group-libraries-tmpl">
<span class="loading-icon loading-tip"></span>
<table class="hide">
<thead>
<tr>
<th width="4%"><!--icon--></th>
<th width="35%">{% trans "Name" %}</th>
<th width="20%">{% trans "Size" %}</th>
<th width="26%">{% trans "Shared By" %}</th>
<th width="15%">{% trans "Operations" %}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="empty-tips hide">
<h2 class="alc">{% trans "No libraries" %}</h2>
</div>
</script>
<script type="text/template" id="group-library-item-tmpl">
<td>
<img src="<%= icon_url %>" title="<%= icon_title %>" alt="<%= icon_title %>" width="24" />
</td>
<td>
<% if (enable_sys_admin_view_repo && is_pro && !encrypted) { %>
<a href="#libs/<%= repo_id %>/"><%- name %></a>
<% } else { %>
<%- name %>
<% } %>
</td>
<td><%- formatted_size %></td>
<td><a href="{{ SITE_ROOT }}useradmin/info/<% print(encodeURIComponent(shared_by)); %>/"><%- shared_by %></a></td>
<td>
<a href="#" class="sf2-icon-delete sf2-x repo-unshare-btn op-icon vh" title="{% trans "Unshare" %}" aria-label="{% trans "Unshare" %}"></a>
</td>
</script>
<script type="text/template" id="group-members-tmpl">
<span class="loading-icon loading-tip"></span>
<table class="hide">
<thead>
<tr>
<th width="5%"></th>
<th width="25%">{% trans "Name" %}</th>
<th width="35%">{% trans "Email" %}</th>
<th width="25%">{% trans "Role" %}</th>
<th width="10%"><!--Operations--></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="empty-tips hide">
<h2 class="alc">{% trans "No members" %}</h2>
</div>
</script>
<script type="text/template" id="group-member-item-tmpl">
<td><img src="<%= avatar_url %>" alt="" width="20" class="avatar" /></td>
<td><%- name %></td>
<td><a href="{{ SITE_ROOT }}useradmin/info/<% print(encodeURIComponent(email)); %>/"><%- email %></a></td>
<td>
<% if (role == 'Owner') { %>
<span class="cur-role">{% trans "Owner" %}</span>
<% } else if (role == 'Admin') { %>
<span class="cur-role">{% trans "Admin" %}</span>
<span title="{% trans "Edit" %}" class="user-role-edit-icon sf2-icon-edit op-icon vh"></span>
<select name="role" class="user-role-select hide">
<option value="0">{% trans "Member" %}</option>
<option value="1" selected="selected">{% trans "Admin" %}</option>
</select>
<% } else if (role == 'Member') { %>
<span class="cur-role">{% trans "Member" %}</span>
<span title="{% trans "Edit" %}" class="user-role-edit-icon sf2-icon-edit op-icon vh"></span>
<select name="role" class="user-role-select hide">
<option value="0" selected="selected">{% trans "Member" %}</option>
<option value="1">{% trans "Admin" %}</option>
</select>
<% } %>
</td>
<td>
<% if (role != 'Owner') { %>
<a href="#" class="sf2-icon-delete sf2-x member-delete-btn op-icon vh" title="{% trans "Delete" %}" aria-label="{% trans "Delete" %}"></a>
<% } %>
</td>
</script>

View File

@@ -1,90 +0,0 @@
{% extends "sysadmin/base.html" %}
{% load i18n group_avatar_tags avatar_tags seahub_tags %}
{% load url from future %}
{% block right_panel %}
<p class="path-bar">
<a class="normal" href="{{ SITE_ROOT }}sysadmin/#groups/">Groups</a>
<span class="path-split">/</span>
{{ group.group_name }}
</p>
<div id="tabs" class="tab-tabs">
<div class="hd ovhd">
<ul class="tab-tabs-nav fleft">
<li class="tab"><a href="#library" class="a">{% trans "Libraries" %}</a></li>
<li class="tab"><a href="#member" class="a">{% trans "Members" %}</a></li>
</ul>
</div>
<div id="library">
{% if repos %}
<table class="repo-list">
<tr>
<th width="4%"><!--icon--></th>
<th width="35%">{% trans "Name" %}</th>
<th width="20%">{% trans "Size" %}</th>
<th width="26%">{% trans "Shared By" %}</th>
<th width="15%">{% trans "Operations" %}</th>
</tr>
{% for repo in repos %}
<tr>
{% if repo.encrypted %}
<td><img src="{{MEDIA_URL}}img/sync-folder-encrypt-20.png" title="{% trans "Encrypted"%}" alt="{% trans "library icon" %}" /></td>
{% elif repo.permission == 'r' %}
<td><img src="{{MEDIA_URL}}img/folder-no-write-20.png" title="{% trans "Read-Only"%}" 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 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 %}
<td>{{ repo.size|filesizeformat }}</td>
<td><a href="{% url 'user_info' repo.user %}">{{ repo.user }}</a></td>
<td data-id="{{ repo.id }}" data-name="{{repo.name}}">
<div><a href="#" data-url="{% url "sys_repo_delete" repo.id %}" data-target="{{ repo.name }}" class="repo-delete-btn op-icon sf2-icon-delete vh" title="{% trans "Delete" %}"></a></div>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<div class="empty-tips">
<h2 class="alc">{% trans "No library has shared to this group" %}</h2>
</div>
{% endif %}
</div>
<div id="member">
<ul class="user-list">
{% for m in members %}
<li class="user ovhd">
<a href="{% url 'user_info' m.user_name %}" class="pic fleft">{% avatar m.user_name 48 %}</a>
<div class="txt fright">
<a class="name" href="{% url 'user_info' m.user_name %}">{{ m.user_name|email2nickname }}</a>{% if m.is_staff %}<span> ({% trans "admin" %})</span>{% endif %}
<p>{{ m.user_name }}</p>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}
{% block extra_script %}
<script type="text/javascript">
addConfirmTo($('.repo-delete-btn'), {
'title': "{% trans "Delete Library" %}",
'con': "{% trans "Are you sure you want to delete %s ?" %}",
'post': true
});
</script>
{% endblock %}

View File

@@ -256,7 +256,7 @@
</tr>
{% for group in personal_groups %}
<tr>
<td><a href="{% url 'sys_admin_group_info' group.id %}">{{ group.group_name }}</a></td>
<td><a href="{{ SITE_ROOT }}sysadmin/#groups/{{ group.id }}/libs/">{{ group.group_name }}</a></td>
<td>{{ group.role }}</td>
<td>{{ group.timestamp|tsstr_sec }}</td>
<td><a href="#" data-url="{% url 'group_remove' group.id %}" data-target="{{ group.group_name }}" class="rm-grp op vh">{% trans "Delete" %}</a></td>

View File

@@ -48,6 +48,8 @@ from seahub.api2.endpoints.admin.library_dirents import AdminLibraryDirents, Adm
from seahub.api2.endpoints.admin.system_library import AdminSystemLibrary
from seahub.api2.endpoints.admin.trash_libraries import AdminTrashLibraries, AdminTrashLibrary
from seahub.api2.endpoints.admin.groups import AdminGroups, AdminGroup
from seahub.api2.endpoints.admin.group_libraries import AdminGroupLibraries, AdminGroupLibrary
from seahub.api2.endpoints.admin.group_members import AdminGroupMembers, AdminGroupMember
from seahub.api2.endpoints.admin.shares import AdminShares
# Uncomment the next two lines to enable the admin:
@@ -214,6 +216,10 @@ urlpatterns = patterns(
url(r'^api/v2.1/admin/libraries/(?P<repo_id>[-0-9a-f]{36})/dirents/$', AdminLibraryDirents.as_view(), name='api-v2.1-admin-library-dirents'),
url(r'^api/v2.1/admin/groups/$', AdminGroups.as_view(), name='api-v2.1-admin-groups'),
url(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/$', AdminGroup.as_view(), name='api-v2.1-admin-group'),
url(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/libraries/$', AdminGroupLibraries.as_view(), name='api-v2.1-admin-group-libraries'),
url(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/libraries/(?P<repo_id>[-0-9a-f]{36})/$', AdminGroupLibrary.as_view(), name='api-v2.1-admin-group-library'),
url(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/members/$', AdminGroupMembers.as_view(), name='api-v2.1-admin-group-members'),
url(r'^api/v2.1/admin/groups/(?P<group_id>\d+)/members/(?P<email>[^/]+)/$', AdminGroupMember.as_view(), name='api-v2.1-admin-group-member'),
url(r'^api/v2.1/admin/libraries/(?P<repo_id>[-0-9a-f]{36})/dirent/$', AdminLibraryDirent.as_view(), name='api-v2.1-admin-library-dirent'),
url(r'^api/v2.1/admin/system-library/$', AdminSystemLibrary.as_view(), name='api-v2.1-admin-system-library'),
url(r'^api/v2.1/admin/trash-libraries/$', AdminTrashLibraries.as_view(), name='api-v2.1-admin-trash-libraries'),
@@ -253,7 +259,6 @@ urlpatterns = patterns(
url(r'^sys/useradmin/ldap/imported$', sys_user_admin_ldap_imported, name='sys_useradmin_ldap_imported'),
url(r'^sys/useradmin/admins/$', sys_user_admin_admins, name='sys_useradmin_admins'),
url(r'^sys/groupadmin/export-excel/$', sys_group_admin_export_excel, name='sys_group_admin_export_excel'),
url(r'^sys/groupadmin/(?P<group_id>\d+)/$', sys_admin_group_info, name='sys_admin_group_info'),
url(r'^sys/orgadmin/$', sys_org_admin, name='sys_org_admin'),
url(r'^sys/orgadmin/search/$', sys_org_search, name='sys_org_search'),
url(r'^sys/orgadmin/(?P<org_id>\d+)/set_quota/$', sys_org_set_quota, name='sys_org_set_quota'),

View File

@@ -1064,26 +1064,6 @@ def sys_group_admin_export_excel(request):
wb.save(response)
return response
@login_required
@sys_staff_required
def sys_admin_group_info(request, group_id):
group_id = int(group_id)
group = get_group(group_id)
org_id = request.GET.get('org_id', None)
if org_id:
repos = seafile_api.get_org_group_repos(org_id, group_id)
else:
repos = seafile_api.get_repos_by_group(group_id)
members = get_group_members(group_id)
return render_to_response('sysadmin/sys_admin_group_info.html', {
'group': group,
'repos': repos,
'members': members,
'enable_sys_admin_view_repo': ENABLE_SYS_ADMIN_VIEW_REPO,
}, context_instance=RequestContext(request))
@login_required
@sys_staff_required
def sys_org_admin(request):

View File

@@ -167,7 +167,7 @@ define([
var err_str = '';
if (data.failed.length > 0) {
$(data.failed).each(function(index, item) {
err_str += item.email + ': ' + item.error_msg + '<br />';
err_str += Common.HTMLescape(item.email) + ': ' + Common.HTMLescape(item.error_msg) + '<br />';
});
_this.$error.html(err_str).show();
}

View File

@@ -270,7 +270,7 @@ define([
if (data.failed.length > 0) {
var err_msg = '';
$(data.failed).each(function(index, item) {
err_msg += item.email + ': ' + item.error_msg + '<br />';
err_msg += Common.HTMLescape(item.email) + ': ' + Common.HTMLescape(item.error_msg) + '<br />';
});
$error.html(err_msg).removeClass('hide');
Common.enableButton($submitBtn);

View File

@@ -179,6 +179,10 @@ define([
case 'admin-library-dirents': return siteRoot + 'api/v2.1/admin/libraries/' + options.repo_id + '/dirents/';
case 'admin-groups': return siteRoot + 'api/v2.1/admin/groups/';
case 'admin-group': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/';
case 'admin-group-libraries': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/libraries/';
case 'admin-group-library': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/libraries/' + options.repo_id + '/';
case 'admin-group-members': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/members/';
case 'admin-group-member': return siteRoot + 'api/v2.1/admin/groups/' + options.group_id + '/members/' + options.email+ '/';
case 'admin-system-library': return siteRoot + 'api/v2.1/admin/system-library/';
case 'admin-trash-libraries': return siteRoot + 'api/v2.1/admin/trash-libraries/';
case 'admin-trash-library': return siteRoot + 'api/v2.1/admin/trash-libraries/' + options.repo_id + '/';
@@ -286,6 +290,16 @@ define([
}
},
getLibIconTitle: function(is_encrypted, is_readonly) {
if (is_encrypted) {
return gettext("Encrypted library");
} else if (is_readonly) {
return gettext("Read-Only library");
} else {
return gettext("Read-Write library");
}
},
isHiDPI: function() {
var pixelRatio = window.devicePixelRatio ? window.devicePixelRatio : 1;
if (pixelRatio > 1) {

View File

@@ -0,0 +1,29 @@
define([
'underscore',
'backbone',
'common',
'sysadmin-app/models/group-member'
], function(_, Backbone, Common, GroupMemberModel) {
'use strict';
var GroupMemberCollection = Backbone.Collection.extend({
model: GroupMemberModel,
setGroupId: function(group_id) {
this.group_id = group_id;
},
parse: function (data) {
this.group_name= data.group_name;
return data.members; // return the array
},
url: function () {
return Common.getUrl({name: 'admin-group-members', group_id: this.group_id});
}
});
return GroupMemberCollection;
});

View File

@@ -0,0 +1,29 @@
define([
'underscore',
'backbone',
'common',
'sysadmin-app/models/group-repo'
], function(_, Backbone, Common, GroupRepoModel) {
'use strict';
var GroupRepoCollection = Backbone.Collection.extend({
model: GroupRepoModel,
setGroupId: function(group_id) {
this.group_id = group_id;
},
parse: function (data) {
this.group_name= data.group_name;
return data.libraries; // return the array
},
url: function () {
return Common.getUrl({name: 'admin-group-libraries', group_id: this.group_id});
}
});
return GroupRepoCollection;
});

View File

@@ -0,0 +1,12 @@
define([
'underscore',
'backbone',
'common'
], function(_, Backbone, Common) {
'use strict';
var GroupMember = Backbone.Model.extend({});
return GroupMember;
});

View File

@@ -0,0 +1,25 @@
define([
'underscore',
'backbone',
'common'
], function(_, Backbone, Common) {
'use strict';
var GroupRepo = Backbone.Model.extend({
getIconUrl: function(size) {
var is_encrypted = this.get('encrypted');
var is_readonly = this.get('permission') == "r" ? true : false;
return Common.getLibIconUrl(is_encrypted, is_readonly, size);
},
getIconTitle: function() {
var is_encrypted = this.get('encrypted');
var is_readonly = this.get('permission') == "r" ? true : false;
return Common.getLibIconTitle(is_encrypted, is_readonly);
}
});
return GroupRepo;
});

View File

@@ -12,14 +12,8 @@ define([
},
getIconTitle: function() {
var icon_title = '';
if (this.get('encrypted')) {
icon_title = gettext("Encrypted library");
} else {
icon_title = gettext("Read-Write library");
}
return icon_title;
var is_encrypted = this.get('encrypted');
return Common.getLibIconTitle(is_encrypted, false);
}
});

View File

@@ -16,12 +16,14 @@ define([
'sysadmin-app/views/dir',
'sysadmin-app/views/groups',
'sysadmin-app/views/search-groups',
'sysadmin-app/views/group-repos',
'sysadmin-app/views/group-members',
'app/views/account'
], function($, Backbone, Common, SideNavView, DashboardView,
DesktopDevicesView, MobileDevicesView, DeviceErrorsView,
ReposView, SearchReposView, SystemReposView, TrashReposView,
SearchTrashReposView, DirView, GroupsView, SearchGroupsView,
AccountView) {
GroupReposView, GroupMembersView, AccountView) {
"use strict";
@@ -40,6 +42,8 @@ define([
'libs/:repo_id(/*path)': 'showLibraryDir',
'groups/': 'showGroups',
'search-groups/': 'showSearchGroups',
'groups/:group_id/libs/': 'showGroupLibraries',
'groups/:group_id/members/': 'showGroupMembers',
// Default
'*actions': 'showDashboard'
},
@@ -69,6 +73,8 @@ define([
this.groupsView = new GroupsView();
this.searchGroupsView = new SearchGroupsView();
this.groupReposView = new GroupReposView();
this.groupMembersView = new GroupMembersView();
app.ui.accountView = this.accountView = new AccountView();
@@ -205,6 +211,18 @@ define([
this.searchGroupsView.show({
'name': decodeURIComponent(group_name)
});
},
showGroupLibraries: function(group_id) {
this.switchCurrentView(this.groupReposView);
this.sideNavView.setCurTab('groups');
this.groupReposView.show(group_id);
},
showGroupMembers: function(group_id) {
this.switchCurrentView(this.groupMembersView);
this.sideNavView.setCurTab('groups');
this.groupMembersView.show(group_id);
}
});

View File

@@ -0,0 +1,118 @@
define([
'jquery',
'underscore',
'backbone',
'common',
'app/views/widgets/hl-item-view'
], function($, _, Backbone, Common, HLItemView) {
'use strict';
var GroupMemberView = HLItemView.extend({
tagName: 'tr',
template: _.template($('#group-member-item-tmpl').html()),
events: {
'click .user-role-edit-icon': 'showEdit',
'change .user-role-select': 'editRole',
'click .member-delete-btn': 'deleteGroupMember'
},
initialize: function() {
HLItemView.prototype.initialize.call(this);
this.listenTo(this.model, 'change', this.render);
var _this = this;
$(document).on('click', function(e) {
var target = e.target || event.srcElement;
if (!_this.$('.user-role-edit-icon, .user-role-select').is(target)) {
_this.$('.cur-role, .user-role-edit-icon').show();
_this.$('.user-role-select').hide();
}
});
},
showEdit: function() {
this.$('.cur-role, .user-role-edit-icon').hide();
this.$('.user-role-select').show();
},
editRole: function() {
var _this = this;
// '0': member, '1': admin
var val = this.$('[name="role"]').val();
var is_admin = val == 1 ? true : false;
$.ajax({
url: Common.getUrl({
'name': 'admin-group-member',
'group_id': _this.model.get('group_id'),
'email': _this.model.get('email')
}),
type: 'put',
dataType: 'json',
beforeSend: Common.prepareCSRFToken,
data: {
'is_admin': is_admin
},
success: function(data) {
_this.model.set({
'is_admin': data['is_admin'],
'role': data['role']
});
},
error: function(xhr) {
var err_msg;
if (xhr.responseText) {
err_msg = $.parseJSON(xhr.responseText).error_msg;
} else {
err_msg = gettext("Failed. Please check the network.");
}
Common.feedback(err_msg, 'error');
}
});
},
deleteGroupMember: function() {
var _this = this;
var email = this.model.get('email');
var popupTitle = gettext("Delete Member");
var popupContent = gettext("Are you sure you want to delete %s ?").replace('%s', '<span class="op-target ellipsis ellipsis-op-target" title="' + Common.HTMLescape(email) + '">' + Common.HTMLescape(email) + '</span>');
var yesCallback = function() {
$.ajax({
url: Common.getUrl({
'name': 'admin-group-member',
'group_id': _this.model.get('group_id'),
'email': email
}),
type: 'DELETE',
beforeSend: Common.prepareCSRFToken,
dataType: 'json',
success: function() {
_this.$el.remove();
var msg = gettext("Successfully deleted member {placeholder}").replace('{placeholder}', email);
Common.feedback(msg, 'success');
},
error: function(xhr, textStatus, errorThrown) {
Common.ajaxErrorHandler(xhr, textStatus, errorThrown);
},
complete: function() {
$.modal.close();
}
});
};
Common.showConfirm(popupTitle, popupContent, yesCallback);
return false;
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
return GroupMemberView;
});

View File

@@ -0,0 +1,187 @@
define([
'jquery',
'underscore',
'backbone',
'common',
'sysadmin-app/views/group-member',
'sysadmin-app/collection/group-members'
], function($, _, Backbone, Common, GroupMemberView, GroupMemberCollection) {
'use strict';
var GroupMembersView = Backbone.View.extend({
id: 'admin-groups',
tabNavTemplate: _.template($("#groups-tabnav-tmpl").html()),
template: _.template($("#group-members-tmpl").html()),
addMemberFormTemplate: _.template($('#add-group-member-form-tmpl').html()),
initialize: function() {
this.groupMemberCollection = new GroupMemberCollection();
this.listenTo(this.groupMemberCollection, 'add', this.addOne);
this.listenTo(this.groupMemberCollection, 'reset', this.reset);
},
events: {
'click #js-add-group-member': 'addGroupMember'
},
addGroupMember: function () {
var $form = $(this.addMemberFormTemplate()),
_this = this;
$form.modal();
$('#simplemodal-container').css({'height':'auto', 'width':'auto'});
$('[name="email"]', $form).select2($.extend(
Common.contactInputOptionsForSelect2(), {
width: '275px',
containerCss: {'margin-bottom': '5px'},
placeholder: gettext("Search user or enter email and press Enter")
}));
$form.submit(function() {
var group_id = _this.groupMemberCollection.group_id;
var emails = $.trim($('[name="email"]', $form).val());
var $error = $('.error', $form);
var $submitBtn = $('[type="submit"]', $form);
if (!emails) {
$error.html(gettext("Email is required.")).show();
return false;
}
var input_emails = [];
var emails_list = emails.split(',');
for (var i = 0; i < emails_list.length; i++) {
input_emails.push(emails_list[i]);
}
$error.hide();
Common.disableButton($submitBtn);
$.ajax({
url: Common.getUrl({
'name': 'admin-group-members',
'group_id': group_id
}),
type: 'POST',
dataType: 'json',
beforeSend: Common.prepareCSRFToken,
traditional: true,
data: {'email': input_emails},
success: function(data) {
if (data.success.length > 0) {
_this.groupMemberCollection.add(data.success, {prepend: true});
}
var err_str = '';
if (data.failed.length > 0) {
$(data.failed).each(function(index, item) {
err_str += Common.HTMLescape(item.email) + ': ' + Common.HTMLescape(item.error_msg) + '<br />';
});
$error.html(err_str).show();
Common.enableButton($submitBtn);
} else {
Common.closeModal();
}
},
error: function(jqXHR, textStatus, errorThrown){
var err_msg;
if (jqXHR.responseText) {
err_msg = jqXHR.responseJSON.error_msg;
} else {
err_msg = gettext('Please check the network.');
}
$error.html(err_msg).show();
Common.enableButton($submitBtn);
}
});
return false;
});
return false;
},
render: function() {
var group_id = this.groupMemberCollection.group_id;
this.$el.html(this.tabNavTemplate({'cur_tab': 'members', 'group_id': group_id}) + this.template());
this.$table = this.$('table');
this.$tableBody = $('tbody', this.$table);
this.$loadingTip = this.$('.loading-tip');
this.$emptyTip = this.$('.empty-tips');
},
initPage: function() {
this.$table.hide();
this.$tableBody.empty();
this.$loadingTip.show();
this.$emptyTip.hide();
},
hide: function() {
this.$el.detach();
this.attached = false;
},
show: function(group_id) {
if (!this.attached) {
this.attached = true;
$("#right-panel").html(this.$el);
}
// init collection
this.groupMemberCollection.setGroupId(group_id);
this.render();
this.showGroupMembers();
},
showGroupMembers: function() {
this.initPage();
var _this = this;
this.groupMemberCollection.fetch({
cache: false,
reset: true,
error: function(collection, response, opts) {
var err_msg;
if (response.responseText) {
if (response['status'] == 401 || response['status'] == 403) {
err_msg = gettext("Permission error");
} else {
err_msg = $.parseJSON(response.responseText).error_msg;
}
} else {
err_msg = gettext("Failed. Please check the network.");
}
Common.feedback(err_msg, 'error');
}
});
},
reset: function() {
this.$loadingTip.hide();
if (this.groupMemberCollection.length > 0) {
this.groupMemberCollection.each(this.addOne, this);
this.$table.show();
} else {
this.$emptyTip.show();
}
this.$('.path-bar').append(this.groupMemberCollection.group_name);
},
addOne: function(item, collection, options) {
var view = new GroupMemberView({model: item});
if (options.prepend) {
this.$tableBody.prepend(view.render().el);
} else {
this.$tableBody.append(view.render().el);
}
}
});
return GroupMembersView;
});

View File

@@ -0,0 +1,75 @@
define([
'jquery',
'underscore',
'backbone',
'common',
'app/views/widgets/hl-item-view'
], function($, _, Backbone, Common, HLItemView) {
'use strict';
var GroupRepoView = HLItemView.extend({
tagName: 'tr',
template: _.template($('#group-library-item-tmpl').html()),
events: {
'click .repo-unshare-btn': 'unshareGroupLibrary'
},
initialize: function() {
HLItemView.prototype.initialize.call(this);
},
unshareGroupLibrary: function() {
var _this = this;
var repo_name = this.model.get('name');
var popupTitle = gettext("Unshare Library");
var popupContent = gettext("Are you sure you want to unshare %s ?").replace('%s', '<span class="op-target ellipsis ellipsis-op-target" title="' + Common.HTMLescape(repo_name) + '">' + Common.HTMLescape(repo_name) + '</span>');
var yesCallback = function() {
$.ajax({
url: Common.getUrl({
'name': 'admin-group-library',
'group_id': _this.model.get('group_id'),
'repo_id': _this.model.get('repo_id')
}),
type: 'DELETE',
beforeSend: Common.prepareCSRFToken,
dataType: 'json',
success: function() {
_this.$el.remove();
var msg = gettext("Successfully unshared library {placeholder}").replace('{placeholder}', repo_name);
Common.feedback(msg, 'success');
},
error: function(xhr, textStatus, errorThrown) {
Common.ajaxErrorHandler(xhr, textStatus, errorThrown);
},
complete: function() {
$.modal.close();
}
});
};
Common.showConfirm(popupTitle, popupContent, yesCallback);
return false;
},
render: function() {
var data = this.model.toJSON(),
icon_size = Common.isHiDPI() ? 96 : 24,
icon_url = this.model.getIconUrl(icon_size);
data['icon_url'] = icon_url;
data['icon_title'] = this.model.getIconTitle();
data['formatted_size'] = Common.fileSizeFormat(data['size'], 1),
data['enable_sys_admin_view_repo'] = app.pageOptions.enable_sys_admin_view_repo;
data['is_pro'] = app.pageOptions.is_pro;
this.$el.html(this.template(data));
return this;
}
});
return GroupRepoView;
});

View File

@@ -0,0 +1,102 @@
define([
'jquery',
'underscore',
'backbone',
'common',
'sysadmin-app/views/group-repo',
'sysadmin-app/collection/group-repos'
], function($, _, Backbone, Common, GroupRepoView, GroupRepoCollection) {
'use strict';
var GroupReposView = Backbone.View.extend({
id: 'admin-groups',
tabNavTemplate: _.template($("#groups-tabnav-tmpl").html()),
template: _.template($("#group-libraries-tmpl").html()),
initialize: function() {
this.groupRepoCollection = new GroupRepoCollection();
this.listenTo(this.groupRepoCollection, 'add', this.addOne);
this.listenTo(this.groupRepoCollection, 'reset', this.reset);
},
render: function() {
var group_id = this.groupRepoCollection.group_id;
this.$el.html(this.tabNavTemplate({'cur_tab': 'libs', 'group_id': group_id}) + this.template());
this.$table = this.$('table');
this.$tableBody = $('tbody', this.$table);
this.$loadingTip = this.$('.loading-tip');
this.$emptyTip = this.$('.empty-tips');
},
initPage: function() {
this.$table.hide();
this.$tableBody.empty();
this.$loadingTip.show();
this.$emptyTip.hide();
},
hide: function() {
this.$el.detach();
this.attached = false;
},
show: function(group_id) {
if (!this.attached) {
this.attached = true;
$("#right-panel").html(this.$el);
}
// init collection
this.groupRepoCollection.setGroupId(group_id);
this.render();
this.showGroupLibraries();
},
showGroupLibraries: function() {
this.initPage();
var _this = this;
this.groupRepoCollection.fetch({
cache: false,
reset: true,
error: function(collection, response, opts) {
var err_msg;
if (response.responseText) {
if (response['status'] == 401 || response['status'] == 403) {
err_msg = gettext("Permission error");
} else {
err_msg = $.parseJSON(response.responseText).error_msg;
}
} else {
err_msg = gettext("Failed. Please check the network.");
}
Common.feedback(err_msg, 'error');
}
});
},
reset: function() {
this.$loadingTip.hide();
if (this.groupRepoCollection.length > 0) {
this.groupRepoCollection.each(this.addOne, this);
this.$table.show();
} else {
this.$emptyTip.show();
}
this.$('.path-bar').append(this.groupRepoCollection.group_name);
},
addOne: function(library) {
var view = new GroupRepoView({model: library});
this.$tableBody.append(view.render().el);
}
});
return GroupReposView;
});

View File

@@ -0,0 +1,64 @@
import json
from seaserv import seafile_api
from django.core.urlresolvers import reverse
from seahub.test_utils import BaseTestCase
class GroupLibrariesTest(BaseTestCase):
def setUp(self):
self.user_name = self.user.username
self.admin_name = self.admin.username
self.group_id = self.group.id
self.repo_id = self.repo.id
seafile_api.set_group_repo(self.repo_id, self.group_id,
self.admin.username, 'r')
def tearDown(self):
self.remove_group()
def test_can_get(self):
self.login_as(self.admin)
url = reverse('api-v2.1-admin-group-libraries', args=[self.group_id])
resp = self.client.get(url)
json_resp = json.loads(resp.content)
assert json_resp['libraries'][0]['repo_id'] == self.repo_id
assert json_resp['group_id'] == self.group_id
def test_can_not_get_if_not_admin(self):
self.login_as(self.user)
url = reverse('api-v2.1-admin-group-libraries', args=[self.group_id])
resp = self.client.get(url)
self.assertEqual(403, resp.status_code)
def test_can_unshare(self):
# make sure repo is shared to group
repos = seafile_api.get_repos_by_group(self.group_id)
assert len(repos) == 1
self.login_as(self.admin)
url = reverse('api-v2.1-admin-group-library', args=[self.group_id, self.repo_id])
resp = self.client.delete(url)
self.assertEqual(200, resp.status_code)
# make sure repo is unshared
repos = seafile_api.get_repos_by_group(self.group_id)
assert len(repos) == 0
def test_can_not_unshare_if_not_admin(self):
# make sure repo is shared to group
repos = seafile_api.get_repos_by_group(self.group_id)
assert len(repos) == 1
self.login_as(self.user)
url = reverse('api-v2.1-admin-group-library', args=[self.group_id, self.repo_id])
resp = self.client.delete(url)
self.assertEqual(403, resp.status_code)
# make sure repo is unshared
repos = seafile_api.get_repos_by_group(self.group_id)
assert len(repos) == 1

View File

@@ -0,0 +1,104 @@
import json
from seaserv import ccnet_api
from django.core.urlresolvers import reverse
from seahub.test_utils import BaseTestCase
class GroupMembersTest(BaseTestCase):
def setUp(self):
self.user_name = self.user.username
self.admin_name = self.admin.username
self.group_id = self.group.id
self.repo_id = self.repo.id
def tearDown(self):
self.remove_group()
def test_can_get(self):
self.login_as(self.admin)
url = reverse('api-v2.1-admin-group-members',
args=[self.group_id])
resp = self.client.get(url)
json_resp = json.loads(resp.content)
assert json_resp['members'][0]['email'] == self.user_name
assert json_resp['group_id'] == self.group_id
def test_can_not_get_if_not_admin(self):
self.login_as(self.user)
url = reverse('api-v2.1-admin-group-members',
args=[self.group_id])
resp = self.client.get(url)
self.assertEqual(403, resp.status_code)
def test_can_add(self):
self.login_as(self.admin)
url = reverse('api-v2.1-admin-group-members',
args=[self.group_id])
data = {'email': self.admin_name}
resp = self.client.post(url, data)
json_resp = json.loads(resp.content)
assert json_resp['success'][0]['group_id'] == self.group_id
assert json_resp['success'][0]['email'] == self.admin_name
def test_can_not_add_if_not_admin(self):
self.login_as(self.user)
url = reverse('api-v2.1-admin-group-members',
args=[self.group_id])
data = {'email': self.admin_name}
resp = self.client.post(url, data)
self.assertEqual(403, resp.status_code)
def test_can_delete_group_member(self):
ccnet_api.group_add_member(self.group_id, self.user_name,
self.admin_name)
# make sure member in group
members = ccnet_api.get_group_members(self.group_id)
assert len(members) == 2
self.login_as(self.admin)
url = reverse('api-v2.1-admin-group-member',
args=[self.group_id, self.admin_name])
resp = self.client.delete(url)
self.assertEqual(200, resp.status_code)
# make sure member is deleted
members = ccnet_api.get_group_members(self.group_id)
assert len(members) == 1
def test_can_not_delete_if_not_admin(self):
ccnet_api.group_add_member(self.group_id, self.user_name,
self.admin_name)
# make sure member in group
members = ccnet_api.get_group_members(self.group_id)
assert len(members) == 2
self.login_as(self.user)
url = reverse('api-v2.1-admin-group-member',
args=[self.group_id, self.admin_name])
resp = self.client.delete(url)
self.assertEqual(403, resp.status_code)
# make sure member is not deleted
members = ccnet_api.get_group_members(self.group_id)
assert len(members) == 2
def test_can_not_delete_group_owner(self):
# make sure member in group
members = ccnet_api.get_group_members(self.group_id)
assert len(members) == 1
self.login_as(self.user)
url = reverse('api-v2.1-admin-group-member',
args=[self.group_id, self.user_name])
resp = self.client.delete(url)
self.assertEqual(403, resp.status_code)

View File

@@ -21,12 +21,6 @@ def test_sudo_mode_required(admin_browser_once):
'the browser should be redirected back to the previous page'
)
b.visit('/sys/groupadmin/')
assert b.path == '/sys/groupadmin/', (
'once the admin enters the password, '
'he would not be asked again within a certain time'
)
@pytest.mark.xfail
def test_sudo_mode_rejects_wrong_password(admin_browser_once):
b = admin_browser_once