admin-users: add view for user groups managment
model-repo: get_user_groups modified to return more information about user model-user_group: 2 new methods added tests-models: mocked repo test becouse of modifications in model
This commit is contained in:
parent
8f4818db12
commit
b5ec70cf90
9 changed files with 276 additions and 12 deletions
|
|
@ -23,7 +23,7 @@ import pytest
|
|||
|
||||
from rhodecode.model.user import UserModel
|
||||
from rhodecode.model.user_group import UserGroupModel
|
||||
from rhodecode.tests import TEST_USER_REGULAR_LOGIN
|
||||
from rhodecode.tests import TEST_USER_ADMIN_EMAIL
|
||||
from rhodecode.api.tests.utils import (
|
||||
build_data, api_call, assert_error, assert_ok, crash, jsonify)
|
||||
|
||||
|
|
@ -33,7 +33,8 @@ class TestUpdateUserGroup(object):
|
|||
@pytest.mark.parametrize("changing_attr, updates", [
|
||||
('group_name', {'group_name': 'new_group_name'}),
|
||||
('group_name', {'group_name': 'test_group_for_update'}),
|
||||
('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
|
||||
# ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
|
||||
('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
|
||||
('active', {'active': False}),
|
||||
('active', {'active': True})
|
||||
])
|
||||
|
|
@ -59,7 +60,8 @@ class TestUpdateUserGroup(object):
|
|||
# TODO: mikhail: decide if we need to test against the commented params
|
||||
# ('group_name', {'group_name': 'new_group_name'}),
|
||||
# ('group_name', {'group_name': 'test_group_for_update'}),
|
||||
('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
|
||||
# ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
|
||||
('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
|
||||
('active', {'active': False}),
|
||||
('active', {'active': True})
|
||||
])
|
||||
|
|
|
|||
|
|
@ -70,6 +70,15 @@ def admin_routes(config):
|
|||
name='edit_user_auth_tokens_delete',
|
||||
pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
|
||||
|
||||
# user groups management
|
||||
config.add_route(
|
||||
name='edit_user_groups_management',
|
||||
pattern='/users/{user_id:\d+}/edit/groups_management')
|
||||
|
||||
config.add_route(
|
||||
name='edit_user_groups_management_updates',
|
||||
pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
settings = config.get_settings()
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import logging
|
|||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
from pyramid.view import view_config
|
||||
from rhodecode_tools.lib.ext_json import json
|
||||
|
||||
from rhodecode.apps._base import BaseAppView
|
||||
from rhodecode.lib.auth import (
|
||||
|
|
@ -30,6 +31,7 @@ from rhodecode.lib import helpers as h
|
|||
from rhodecode.lib.utils import PartialRenderer
|
||||
from rhodecode.lib.utils2 import safe_int, safe_unicode
|
||||
from rhodecode.model.auth_token import AuthTokenModel
|
||||
from rhodecode.model.user_group import UserGroupModel
|
||||
from rhodecode.model.db import User, or_
|
||||
from rhodecode.model.meta import Session
|
||||
|
||||
|
|
@ -235,3 +237,49 @@ class AdminUsersView(BaseAppView):
|
|||
h.flash(_("Auth token successfully deleted"), category='success')
|
||||
|
||||
return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
|
||||
|
||||
|
||||
@LoginRequired()
|
||||
@HasPermissionAllDecorator('hg.admin')
|
||||
@view_config(
|
||||
route_name='edit_user_groups_management', request_method='GET',
|
||||
renderer='rhodecode:templates/admin/users/user_edit.mako')
|
||||
def groups_management(self):
|
||||
c = self.load_default_context()
|
||||
|
||||
user_id = self.request.matchdict.get('user_id')
|
||||
c.user = User.get_or_404(user_id, pyramid_exc=True)
|
||||
c.data = c.user.group_member
|
||||
self._redirect_for_default_user(c.user.username)
|
||||
groups = [UserGroupModel.get_user_groups_as_dict(group.users_group) for group in c.user.group_member]
|
||||
c.groups = json.dumps(groups)
|
||||
c.active = 'groups'
|
||||
|
||||
return self._get_template_context(c)
|
||||
|
||||
|
||||
@LoginRequired()
|
||||
@HasPermissionAllDecorator('hg.admin')
|
||||
@view_config(
|
||||
route_name='edit_user_groups_management_updates', request_method='POST')
|
||||
def groups_management_updates(self):
|
||||
_ = self.request.translate
|
||||
c = self.load_default_context()
|
||||
|
||||
user_id = self.request.matchdict.get('user_id')
|
||||
c.user = User.get_or_404(user_id, pyramid_exc=True)
|
||||
self._redirect_for_default_user(c.user.username)
|
||||
|
||||
users_groups = set(self.request.POST.getall('users_group_id'))
|
||||
users_groups_model = []
|
||||
|
||||
for ugid in users_groups:
|
||||
users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
|
||||
user_group_model = UserGroupModel()
|
||||
user_group_model.change_groups(c.user, users_groups_model)
|
||||
|
||||
Session().commit()
|
||||
c.active = 'user_groups_management'
|
||||
h.flash(_("Groups successfully changed"), category='success')
|
||||
|
||||
return HTTPFound(h.route_path('edit_user_groups_management', user_id=user_id))
|
||||
|
|
|
|||
|
|
@ -1261,14 +1261,15 @@ class UserGroup(Base, BaseModel):
|
|||
|
||||
"""
|
||||
user_group = self
|
||||
|
||||
data = {
|
||||
'users_group_id': user_group.users_group_id,
|
||||
'group_name': user_group.users_group_name,
|
||||
'group_description': user_group.user_group_description,
|
||||
'active': user_group.users_group_active,
|
||||
'owner': user_group.user.username,
|
||||
'owner_email': user_group.user.email,
|
||||
}
|
||||
|
||||
if with_group_members:
|
||||
users = []
|
||||
for user in user_group.members:
|
||||
|
|
|
|||
|
|
@ -197,6 +197,7 @@ class RepoModel(BaseModel):
|
|||
return _users
|
||||
|
||||
def get_user_groups(self, name_contains=None, limit=20, only_active=True):
|
||||
|
||||
# TODO: mikhail: move this method to the UserGroupModel.
|
||||
query = self.sa.query(UserGroup)
|
||||
if only_active:
|
||||
|
|
@ -223,6 +224,12 @@ class RepoModel(BaseModel):
|
|||
'value_display': 'Group: %s (%d members)' % (
|
||||
group.users_group_name, len(group.members),),
|
||||
'value': group.users_group_name,
|
||||
'description': group.user_group_description,
|
||||
'owner': group.user.username,
|
||||
|
||||
'owner_icon': h.gravatar_url(group.user.email, 30),
|
||||
'value_display_owner': h.person(group.user.email),
|
||||
|
||||
'value_type': 'user_group',
|
||||
'active': group.users_group_active,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -512,3 +512,49 @@ class UserGroupModel(BaseModel):
|
|||
else:
|
||||
log.debug('Skipping addition to group %s since it is '
|
||||
'not managed by auth plugins' % gr)
|
||||
|
||||
|
||||
def change_groups(self, user, groups):
|
||||
"""
|
||||
This method changes user group assignment
|
||||
:param user: User
|
||||
:param groups: array of UserGroupModel
|
||||
:return:
|
||||
"""
|
||||
user = self._get_user(user)
|
||||
log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
|
||||
current_groups = user.group_member
|
||||
current_groups = [x.users_group for x in current_groups]
|
||||
|
||||
# calculate from what groups user should be removed/add
|
||||
groups = set(groups)
|
||||
current_groups = set(current_groups)
|
||||
|
||||
groups_to_remove = current_groups - groups
|
||||
groups_to_add = groups - current_groups
|
||||
|
||||
for gr in groups_to_remove:
|
||||
log.debug('Removing user %s from user group %s', user.username, gr.users_group_name)
|
||||
self.remove_user_from_group(gr.users_group_name, user.username)
|
||||
for gr in groups_to_add:
|
||||
log.debug('Adding user %s to user group %s', user.username, gr.users_group_name)
|
||||
UserGroupModel().add_user_to_group(gr.users_group_name, user.username)
|
||||
|
||||
@staticmethod
|
||||
def get_user_groups_as_dict(user_group):
|
||||
import rhodecode.lib.helpers as h
|
||||
|
||||
data = {
|
||||
'users_group_id': user_group.users_group_id,
|
||||
'group_name': user_group.users_group_name,
|
||||
'group_description': user_group.user_group_description,
|
||||
'active': user_group.users_group_active,
|
||||
"owner": user_group.user.username,
|
||||
'owner_icon': h.gravatar_url(user_group.user.email, 30),
|
||||
"owner_data": {'owner': user_group.user.username, 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@
|
|||
<li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li>
|
||||
<li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
|
||||
<li class="${'active' if c.active=='ips' else ''}"><a href="${h.url('edit_user_ips', user_id=c.user.user_id)}">${_('Ip Whitelist')}</a></li>
|
||||
|
||||
<li class="${'active' if c.active=='groups' else ''}">
|
||||
<a href="${h.route_path('edit_user_groups_management', user_id=c.user.user_id)}">${_('User Groups Management')}</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
145
rhodecode/templates/admin/users/user_edit_groups.mako
Normal file
145
rhodecode/templates/admin/users/user_edit_groups.mako
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
## -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">${_('User groups administration')}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="field">
|
||||
<div class="label label-checkbox">
|
||||
<label for="users_group_active">${_('Add user to group')}:</label>
|
||||
</div>
|
||||
<div class="input">
|
||||
${h.text('add_user_to_group', placeholder="user group name", class_="medium")}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="groups_management">
|
||||
${h.secure_form(h.route_path('edit_user_groups_management_updates', user_id=c.user.user_id), method='post')}
|
||||
<div id="repos_list_wrap">
|
||||
<table id="user_group_list_table" class="display"></table>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
${h.submit('save',_('Save'),class_="btn")}
|
||||
</div>
|
||||
${h.end_form()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var api;
|
||||
$(document).ready(function() {
|
||||
|
||||
var get_datatable_count = function(){
|
||||
$('#user_group_count').text(api.page.info().recordsDisplay);
|
||||
};
|
||||
|
||||
$('#user_group_list_table').on('click', 'a.editor_remove', function (e) {
|
||||
e.preventDefault();
|
||||
var row = api.row($(this).closest('tr'));
|
||||
row.remove().draw();
|
||||
} );
|
||||
|
||||
$('#user_group_list_table').DataTable({
|
||||
data: ${c.groups|n},
|
||||
dom: 'rtp',
|
||||
pageLength: ${c.visual.admin_grid_items},
|
||||
order: [[ 0, "asc" ]],
|
||||
columns: [
|
||||
{ data: {"_": "group_name",
|
||||
"sort": "group_name"}, title: "${_('Name')}", className: "td-componentname," ,
|
||||
render: function (data,type,full,meta)
|
||||
{return '<div><i class="icon-group" title="User group">'+data+'</i></div>'}},
|
||||
|
||||
{ data: {"_": "group_description",
|
||||
"sort": "group_description"}, title: "${_('Description')}", className: "td-description" },
|
||||
{ data: {"_": "users_group_id"}, className: "td-user",
|
||||
render: function (data,type,full,meta)
|
||||
{return '<input type="hidden" name="users_group_id" value="'+data+'">'}},
|
||||
{ data: {"_": "active",
|
||||
"sort": "active"}, title: "${_('Active')}", className: "td-active", className: "td-number"},
|
||||
{ data: {"_": "owner_data"}, title: "${_('Owner')}", className: "td-user",
|
||||
render: function (data,type,full,meta)
|
||||
{return '<div class="rc-user tooltip">'+
|
||||
'<img class="gravatar" src="'+ data.owner_icon +'" height="16" width="16">'+
|
||||
data.owner +'</div>'
|
||||
}
|
||||
},
|
||||
{ data: null,
|
||||
title: "${_('Action')}",
|
||||
className: "td-action",
|
||||
defaultContent: '<a href="" class="btn btn-link btn-danger">Delete</a>'
|
||||
},
|
||||
],
|
||||
language: {
|
||||
paginate: DEFAULT_GRID_PAGINATION,
|
||||
emptyTable: _gettext("No user groups available yet.")
|
||||
},
|
||||
"initComplete": function( settings, json ) {
|
||||
var data_grid = $('#user_group_list_table').dataTable();
|
||||
api = data_grid.api();
|
||||
get_datatable_count();
|
||||
}
|
||||
});
|
||||
|
||||
// update the counter when doing search
|
||||
$('#user_group_list_table').on( 'search.dt', function (e,settings) {
|
||||
get_datatable_count();
|
||||
});
|
||||
|
||||
// filter, filter both grids
|
||||
$('#q_filter').on( 'keyup', function () {
|
||||
var user_api = $('#user_group_list_table').dataTable().api();
|
||||
user_api
|
||||
.columns(0)
|
||||
.search(this.value)
|
||||
.draw();
|
||||
});
|
||||
|
||||
// refilter table if page load via back button
|
||||
$("#q_filter").trigger('keyup');
|
||||
|
||||
});
|
||||
|
||||
$('#language').select2({
|
||||
'containerCssClass': "drop-menu",
|
||||
'dropdownCssClass': "drop-menu-dropdown",
|
||||
'dropdownAutoWidth': true
|
||||
});
|
||||
|
||||
|
||||
|
||||
$(document).ready(function(){
|
||||
$("#group_parent_id").select2({
|
||||
'containerCssClass': "drop-menu",
|
||||
'dropdownCssClass': "drop-menu-dropdown",
|
||||
'dropdownAutoWidth': true
|
||||
});
|
||||
|
||||
$('#add_user_to_group').autocomplete({
|
||||
serviceUrl: pyroutes.url('user_group_autocomplete_data'),
|
||||
minChars:2,
|
||||
maxHeight:400,
|
||||
width:300,
|
||||
deferRequestBy: 300, //miliseconds
|
||||
showNoSuggestionNotice: true,
|
||||
params: { user_groups:true },
|
||||
formatResult: autocompleteFormatResult,
|
||||
lookupFilter: autocompleteFilterResult,
|
||||
onSelect: function(element, suggestion){
|
||||
var owner = {owner_icon: suggestion.owner_icon, owner:suggestion.owner};
|
||||
api.row.add(
|
||||
{"active": suggestion.active,
|
||||
"owner_data": owner,
|
||||
"users_group_id": suggestion.id,
|
||||
"group_description": suggestion.description,
|
||||
"group_name": suggestion.value}).draw();
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
|
@ -260,8 +260,9 @@ class TestGetUserGroups(object):
|
|||
user_util.create_user_group(users_group_active=True))
|
||||
|
||||
group_filter = created_groups[-1].users_group_name[-2:]
|
||||
with self._patch_user_group_list():
|
||||
groups = RepoModel().get_user_groups(group_filter)
|
||||
with mock.patch('rhodecode.lib.helpers.gravatar_url'):
|
||||
with self._patch_user_group_list():
|
||||
groups = RepoModel().get_user_groups(group_filter)
|
||||
|
||||
fake_groups = [
|
||||
u for u in groups if u['value'].startswith('test_returns')]
|
||||
|
|
@ -275,9 +276,9 @@ class TestGetUserGroups(object):
|
|||
for i in range(3):
|
||||
created_groups.append(
|
||||
user_util.create_user_group(users_group_active=True))
|
||||
|
||||
with self._patch_user_group_list():
|
||||
groups = RepoModel().get_user_groups('test_returns')
|
||||
with mock.patch('rhodecode.lib.helpers.gravatar_url'):
|
||||
with self._patch_user_group_list():
|
||||
groups = RepoModel().get_user_groups('test_returns')
|
||||
|
||||
fake_groups = [
|
||||
u for u in groups if u['value'].startswith('test_returns')]
|
||||
|
|
@ -287,9 +288,9 @@ class TestGetUserGroups(object):
|
|||
for i in range(4):
|
||||
is_active = i % 2 == 0
|
||||
user_util.create_user_group(users_group_active=is_active)
|
||||
|
||||
with self._patch_user_group_list():
|
||||
groups = RepoModel().get_user_groups()
|
||||
with mock.patch('rhodecode.lib.helpers.gravatar_url'):
|
||||
with self._patch_user_group_list():
|
||||
groups = RepoModel().get_user_groups()
|
||||
expected = ('id', 'icon_link', 'value_display', 'value', 'value_type')
|
||||
for group in groups:
|
||||
assert group['value_type'] is 'user_group'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue