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:
Bartłomiej Wołyńczyk 2017-03-21 20:51:18 +01:00
parent 8f4818db12
commit b5ec70cf90
9 changed files with 276 additions and 12 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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