user-groups: new selector for user group memebers.
- removed old two column selector in replacement of autocomplete list - scales much better for alrge number of users - still allows to select single users and user groups - fixes #4291 (user group shows only username in members)
This commit is contained in:
parent
a218d75e0a
commit
cbafa9652e
15 changed files with 206 additions and 299 deletions
|
|
@ -67,7 +67,6 @@
|
|||
"<%= dirs.js.src %>/rhodecode/utils/ie.js",
|
||||
"<%= dirs.js.src %>/rhodecode/utils/os.js",
|
||||
"<%= dirs.js.src %>/rhodecode/utils/topics.js",
|
||||
"<%= dirs.js.src %>/rhodecode/widgets/multiselect.js",
|
||||
"<%= dirs.js.src %>/rhodecode/init.js",
|
||||
"<%= dirs.js.src %>/rhodecode/codemirror.js",
|
||||
"<%= dirs.js.src %>/rhodecode/comments.js",
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@ def make_map(config):
|
|||
m.connect('delete_user', '/users/{user_id}',
|
||||
action='delete', conditions={'method': ['DELETE']})
|
||||
m.connect('edit_user', '/users/{user_id}/edit',
|
||||
action='edit', conditions={'method': ['GET']})
|
||||
action='edit', conditions={'method': ['GET']}, jsroute=True)
|
||||
m.connect('user', '/users/{user_id}',
|
||||
action='show', conditions={'method': ['GET']})
|
||||
m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
|
||||
|
|
@ -389,7 +389,7 @@ def make_map(config):
|
|||
|
||||
m.connect('edit_user_group_members',
|
||||
'/user_groups/{user_group_id}/edit/members', jsroute=True,
|
||||
action='edit_members', conditions={'method': ['GET']})
|
||||
action='user_group_members', conditions={'method': ['GET']})
|
||||
|
||||
# ADMIN PERMISSIONS ROUTES
|
||||
with rmap.submapper(path_prefix=ADMIN_PREFIX,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ User Groups crud controller for pylons
|
|||
import logging
|
||||
import formencode
|
||||
|
||||
import peppercorn
|
||||
from formencode import htmlfill
|
||||
from pylons import request, tmpl_context as c, url, config
|
||||
from pylons.controllers.util import redirect
|
||||
|
|
@ -40,7 +41,7 @@ from rhodecode.lib.utils import jsonify, action_logger
|
|||
from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
|
||||
from rhodecode.lib.auth import (
|
||||
LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
|
||||
HasPermissionAnyDecorator)
|
||||
HasPermissionAnyDecorator, XHRRequired)
|
||||
from rhodecode.lib.base import BaseController, render
|
||||
from rhodecode.model.permission import PermissionModel
|
||||
from rhodecode.model.scm import UserGroupList
|
||||
|
|
@ -69,13 +70,8 @@ class UserGroupsController(BaseController):
|
|||
def __load_data(self, user_group_id):
|
||||
c.group_members_obj = [x.user for x in c.user_group.members]
|
||||
c.group_members_obj.sort(key=lambda u: u.username.lower())
|
||||
|
||||
c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
|
||||
|
||||
c.available_members = [(x.user_id, x.username)
|
||||
for x in User.query().all()]
|
||||
c.available_members.sort(key=lambda u: u[1].lower())
|
||||
|
||||
def __load_defaults(self, user_group_id):
|
||||
"""
|
||||
Load defaults settings for edit, and update
|
||||
|
|
@ -207,20 +203,21 @@ class UserGroupsController(BaseController):
|
|||
c.active = 'settings'
|
||||
self.__load_data(user_group_id)
|
||||
|
||||
available_members = [safe_unicode(x[0]) for x in c.available_members]
|
||||
|
||||
users_group_form = UserGroupForm(
|
||||
edit=True, old_data=c.user_group.get_dict(),
|
||||
available_members=available_members, allow_disabled=True)()
|
||||
edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
|
||||
|
||||
try:
|
||||
form_result = users_group_form.to_python(request.POST)
|
||||
pstruct = peppercorn.parse(request.POST.items())
|
||||
form_result['users_group_members'] = pstruct['user_group_members']
|
||||
|
||||
UserGroupModel().update(c.user_group, form_result)
|
||||
gr = form_result['users_group_name']
|
||||
updated_user_group = form_result['users_group_name']
|
||||
action_logger(c.rhodecode_user,
|
||||
'admin_updated_users_group:%s' % gr,
|
||||
'admin_updated_users_group:%s' % updated_user_group,
|
||||
None, self.ip_addr, self.sa)
|
||||
h.flash(_('Updated user group %s') % gr, category='success')
|
||||
h.flash(_('Updated user group %s') % updated_user_group,
|
||||
category='success')
|
||||
Session().commit()
|
||||
except formencode.Invalid as errors:
|
||||
defaults = errors.value
|
||||
|
|
@ -462,19 +459,29 @@ class UserGroupsController(BaseController):
|
|||
return render('admin/user_groups/user_group_edit.html')
|
||||
|
||||
@HasUserGroupPermissionAnyDecorator('usergroup.admin')
|
||||
def edit_members(self, user_group_id):
|
||||
@XHRRequired()
|
||||
@jsonify
|
||||
def user_group_members(self, user_group_id):
|
||||
user_group_id = safe_int(user_group_id)
|
||||
c.user_group = UserGroup.get_or_404(user_group_id)
|
||||
c.active = 'members'
|
||||
c.group_members_obj = sorted((x.user for x in c.user_group.members),
|
||||
key=lambda u: u.username.lower())
|
||||
user_group = UserGroup.get_or_404(user_group_id)
|
||||
group_members_obj = sorted((x.user for x in user_group.members),
|
||||
key=lambda u: u.username.lower())
|
||||
|
||||
group_members = [(x.user_id, x.username) for x in c.group_members_obj]
|
||||
group_members = [
|
||||
{
|
||||
'id': user.user_id,
|
||||
'first_name': user.name,
|
||||
'last_name': user.lastname,
|
||||
'username': user.username,
|
||||
'icon_link': h.gravatar_url(user.email, 30),
|
||||
'value_display': h.person(user.email),
|
||||
'value': user.username,
|
||||
'value_type': 'user',
|
||||
'active': user.active,
|
||||
}
|
||||
for user in group_members_obj
|
||||
]
|
||||
|
||||
if request.is_xhr:
|
||||
return jsonify(lambda *a, **k: {
|
||||
'members': group_members
|
||||
})
|
||||
|
||||
c.group_members = group_members
|
||||
return render('admin/user_groups/user_group_edit.html')
|
||||
return {
|
||||
'members': group_members
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,10 +143,8 @@ def UserForm(edit=False, available_languages=[], old_data={}):
|
|||
return _UserForm
|
||||
|
||||
|
||||
def UserGroupForm(edit=False, old_data=None, available_members=None,
|
||||
allow_disabled=False):
|
||||
def UserGroupForm(edit=False, old_data=None, allow_disabled=False):
|
||||
old_data = old_data or {}
|
||||
available_members = available_members or []
|
||||
|
||||
class _UserGroupForm(formencode.Schema):
|
||||
allow_extra_fields = True
|
||||
|
|
@ -162,10 +160,6 @@ def UserGroupForm(edit=False, old_data=None, available_members=None,
|
|||
users_group_active = v.StringBoolean(if_missing=False)
|
||||
|
||||
if edit:
|
||||
users_group_members = v.OneOf(
|
||||
available_members, hideList=False, testValueList=True,
|
||||
if_missing=None, not_empty=False
|
||||
)
|
||||
# this is user group owner
|
||||
user = All(
|
||||
v.UnicodeString(not_empty=True),
|
||||
|
|
|
|||
|
|
@ -189,18 +189,15 @@ class UserGroupModel(BaseModel):
|
|||
self._log_user_changes('removed from', user_group, removed)
|
||||
|
||||
def _clean_members_data(self, members_data):
|
||||
# TODO: anderson: this should be in the form validation but I couldn't
|
||||
# make it work there as it conflicts with the other validator
|
||||
if not members_data:
|
||||
members_data = []
|
||||
|
||||
if isinstance(members_data, basestring):
|
||||
new_members = [members_data]
|
||||
else:
|
||||
new_members = members_data
|
||||
|
||||
new_members = [int(uid) for uid in new_members]
|
||||
return new_members
|
||||
members = []
|
||||
for user in members_data:
|
||||
uid = int(user['member_user_id'])
|
||||
if uid not in members and user['type'] in ['new', 'existing']:
|
||||
members.append(uid)
|
||||
return members
|
||||
|
||||
def update(self, user_group, form_data):
|
||||
user_group = self._get_user_group(user_group)
|
||||
|
|
|
|||
|
|
@ -1370,7 +1370,27 @@ table.integrations {
|
|||
margin-right: .5em;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.to-delete {
|
||||
.user {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// new entry in group_members
|
||||
.td-author-new-entry {
|
||||
background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
|
||||
}
|
||||
|
||||
.usergroup_member_remove {
|
||||
width: 16px;
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
color: black !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.reviewer_ac .ac-input {
|
||||
width: 92%;
|
||||
margin-bottom: 1em;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ function registerRCRoutes() {
|
|||
pyroutes.register('user_autocomplete_data', '/_users', []);
|
||||
pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
|
||||
pyroutes.register('new_repo', '/_admin/create_repository', []);
|
||||
pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
|
||||
pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
|
||||
pyroutes.register('gists', '/_admin/gists', []);
|
||||
pyroutes.register('new_gist', '/_admin/gists/new', []);
|
||||
|
|
|
|||
|
|
@ -1,132 +0,0 @@
|
|||
// # Copyright (C) 2010-2016 RhodeCode GmbH
|
||||
// #
|
||||
// # This program is free software: you can redistribute it and/or modify
|
||||
// # it under the terms of the GNU Affero General Public License, version 3
|
||||
// # (only), as published by the Free Software Foundation.
|
||||
// #
|
||||
// # This program is distributed in the hope that it will be useful,
|
||||
// # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// # GNU General Public License for more details.
|
||||
// #
|
||||
// # You should have received a copy of the GNU Affero General Public License
|
||||
// # along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// #
|
||||
// # This program is dual-licensed. If you wish to learn more about the
|
||||
// # RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
// # and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
/**
|
||||
* Multi select widget
|
||||
*/
|
||||
var MultiSelectWidget = function(selected_id, available_id, form_id){
|
||||
// definition of containers ID's
|
||||
var selected_container = selected_id;
|
||||
var available_container = available_id;
|
||||
// temp container for selected storage.
|
||||
var cache = [];
|
||||
var av_cache = [];
|
||||
var c = $('#'+selected_container).get(0);
|
||||
var ac = $('#'+available_container).get(0);
|
||||
// get only selected options for further fullfilment
|
||||
for (var i = 0; node = c.options[i]; i++){
|
||||
if (node.selected){
|
||||
// push selected to my temp storage left overs :)
|
||||
cache.push(node);
|
||||
}
|
||||
}
|
||||
// get all available options to cache
|
||||
for (i = 0; node = ac.options[i]; i++){
|
||||
// push selected to my temp storage left overs :)
|
||||
av_cache.push(node);
|
||||
}
|
||||
// fill available only with those not in chosen
|
||||
ac.options.length = 0;
|
||||
tmp_cache = [];
|
||||
|
||||
for (i = 0; node = av_cache[i]; i++){
|
||||
var add = true;
|
||||
for (var i2 = 0; node_2 = cache[i2]; i2++){
|
||||
if (node.value === node_2.value){
|
||||
add=false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(add){
|
||||
tmp_cache.push(new Option(node.text, node.value, false, false));
|
||||
}
|
||||
}
|
||||
for (i = 0; node = tmp_cache[i]; i++){
|
||||
ac.options[i] = node;
|
||||
}
|
||||
function prompts_action_callback(e){
|
||||
var chosen = $('#'+selected_container).get(0);
|
||||
var available = $('#'+available_container).get(0);
|
||||
// get checked and unchecked options from field
|
||||
function get_checked(from_field){
|
||||
// temp container for storage.
|
||||
var sel_cache = [];
|
||||
var oth_cache = [];
|
||||
|
||||
for (i = 0; node = from_field.options[i]; i++){
|
||||
if(node.selected){
|
||||
// push selected fields :)
|
||||
sel_cache.push(node);
|
||||
}
|
||||
else {
|
||||
oth_cache.push(node);
|
||||
}
|
||||
}
|
||||
return [sel_cache,oth_cache];
|
||||
}
|
||||
// fill the field with given options
|
||||
function fill_with(field,options){
|
||||
// clear firtst
|
||||
field.options.length=0;
|
||||
for (var i = 0; node = options[i]; i++){
|
||||
field.options[i] = new Option(node.text, node.value, false, false);
|
||||
}
|
||||
}
|
||||
// adds to current field
|
||||
function add_to(field,options){
|
||||
for (i = 0; node = options[i]; i++){
|
||||
field.appendChild(new Option(node.text, node.value, false, false));
|
||||
}
|
||||
}
|
||||
// add action
|
||||
if (this.id ==='add_element'){
|
||||
var c = get_checked(available);
|
||||
add_to(chosen,c[0]);
|
||||
fill_with(available,c[1]);
|
||||
}
|
||||
// remove action
|
||||
if (this.id ==='remove_element'){
|
||||
c = get_checked(chosen);
|
||||
add_to(available,c[0]);
|
||||
fill_with(chosen,c[1]);
|
||||
}
|
||||
// add all elements
|
||||
if(this.id === 'add_all_elements'){
|
||||
for (i=0; node = available.options[i]; i++){
|
||||
chosen.appendChild(new Option(node.text, node.value, false, false));
|
||||
}
|
||||
available.options.length = 0;
|
||||
}
|
||||
// remove all elements
|
||||
if (this.id === 'remove_all_elements'){
|
||||
for (i=0; node = chosen.options[i]; i++){
|
||||
available.appendChild(new Option(node.text, node.value, false, false));
|
||||
}
|
||||
chosen.options.length = 0;
|
||||
}
|
||||
}
|
||||
$('#add_element, #remove_element, #add_all_elements, #remove_all_elements').click(prompts_action_callback);
|
||||
if (form_id !== undefined) {
|
||||
$('#'+form_id).submit(function(){
|
||||
var chosen = $('#'+selected_container).get(0);
|
||||
for (i = 0; i < chosen.options.length; i++) {
|
||||
chosen.options[i].selected = 'selected';
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -35,7 +35,6 @@
|
|||
<li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_group_advanced', user_group_id=c.user_group.users_group_id)}">${_('Advanced')}</a></li>
|
||||
<li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_group_global_perms', user_group_id=c.user_group.users_group_id)}">${_('Global permissions')}</a></li>
|
||||
<li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_group_perms_summary', user_group_id=c.user_group.users_group_id)}">${_('Permissions summary')}</a></li>
|
||||
<li class="${'active' if c.active=='members' else ''}"><a href="${h.url('edit_user_group_members', user_group_id=c.user_group.users_group_id)}">${_('Members')}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
<%namespace name="base" file="/base/base.html"/>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">${_('Members of User Group: %s') % c.user_group.users_group_name}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
% if c.group_members_obj:
|
||||
<table class="rctable group_members">
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Name</th>
|
||||
</tr>
|
||||
%for user in c.group_members_obj:
|
||||
<tr>
|
||||
<td class="td-author">
|
||||
<div class="group_member">
|
||||
${base.gravatar(user.email, 16)}
|
||||
<span class="username user">${h.link_to(user.username, h.url( 'edit_user',user_id=user.user_id))}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="fullname">${user.full_name}</td>
|
||||
</tr>
|
||||
%endfor
|
||||
</table>
|
||||
%else:
|
||||
<p class="empty_data">${_('No members yet')}</p>
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -54,40 +54,53 @@
|
|||
${h.checkbox('users_group_active',value=True)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
<label for="users_group_active">${_('Search')}:</label>
|
||||
${h.text('from_user_group',
|
||||
placeholder="user/usergroup",
|
||||
class_="medium")}
|
||||
|
||||
<div class="field">
|
||||
<div class="label label-checkbox">
|
||||
<label for="users_group_active">${_('Add members')}:</label>
|
||||
</div>
|
||||
<div class="select side-by-side-selector">
|
||||
<div class="left-group">
|
||||
<label class="text"><strong>${_('Chosen group members')}</strong></label>
|
||||
${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,)}
|
||||
<div class="btn" id="remove_all_elements" >
|
||||
${_('Remove all elements')}
|
||||
<i class="icon-chevron-right"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle-group">
|
||||
<i id="add_element" class="icon-chevron-left"></i>
|
||||
<br />
|
||||
<i id="remove_element" class="icon-chevron-right"></i>
|
||||
</div>
|
||||
<div class="right-group">
|
||||
<label class="text" >${_('Available users')}
|
||||
</label>
|
||||
${h.select('available_members',[],c.available_members,multiple=True,size=8,)}
|
||||
<div class="btn" id="add_all_elements" >
|
||||
<i class="icon-chevron-left"></i>${_('Add all elements')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="input">
|
||||
${h.text('user_group_add_members', placeholder="user/usergroup", class_="medium")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
${h.submit('Save',_('Save'),class_="btn")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="__start__" value="user_group_members:sequence"/>
|
||||
<table id="group_members_placeholder" class="rctable group_members">
|
||||
<tr>
|
||||
<th>${_('Username')}</th>
|
||||
<th>${_('Action')}</th>
|
||||
</tr>
|
||||
|
||||
% if c.group_members_obj:
|
||||
% for user in c.group_members_obj:
|
||||
<tr>
|
||||
<td id="member_user_${user.user_id}" class="td-author">
|
||||
<div class="group_member">
|
||||
${base.gravatar(user.email, 16)}
|
||||
<span class="username user">${h.link_to(h.person(user), h.url( 'edit_user',user_id=user.user_id))}</span>
|
||||
<input type="hidden" name="__start__" value="member:mapping">
|
||||
<input type="hidden" name="member_user_id" value="${user.user_id}">
|
||||
<input type="hidden" name="type" value="existing" id="member_${user.user_id}">
|
||||
<input type="hidden" name="__end__" value="member:mapping">
|
||||
</div>
|
||||
</td>
|
||||
<td class="">
|
||||
<div class="usergroup_member_remove action_button" onclick="removeUserGroupMember(${user.user_id}, true)" style="visibility: visible;">
|
||||
<i class="icon-remove-sign"></i>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
% endfor
|
||||
|
||||
% else:
|
||||
<tr><td colspan="2">${_('No members yet')}</td></tr>
|
||||
% endif
|
||||
</table>
|
||||
<input type="hidden" name="__end__" value="user_group_members:sequence"/>
|
||||
|
||||
<div class="buttons">
|
||||
${h.submit('Save',_('Save'),class_="btn")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${h.end_form()}
|
||||
|
|
@ -95,15 +108,18 @@
|
|||
</div>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
MultiSelectWidget('users_group_members','available_members','edit_users_group');
|
||||
|
||||
$("#group_parent_id").select2({
|
||||
'containerCssClass': "drop-menu",
|
||||
'dropdownCssClass': "drop-menu-dropdown",
|
||||
'dropdownAutoWidth': true
|
||||
});
|
||||
|
||||
$('#from_user_group').autocomplete({
|
||||
removeUserGroupMember = function(userId){
|
||||
$('#member_'+userId).val('remove');
|
||||
$('#member_user_'+userId).addClass('to-delete');
|
||||
};
|
||||
|
||||
$('#user_group_add_members').autocomplete({
|
||||
serviceUrl: pyroutes.url('user_autocomplete_data'),
|
||||
minChars:2,
|
||||
maxHeight:400,
|
||||
|
|
@ -115,9 +131,37 @@
|
|||
lookupFilter: autocompleteFilterResult,
|
||||
onSelect: function(element, suggestion){
|
||||
|
||||
function preSelectUserIds(uids) {
|
||||
$('#available_members').val(uids);
|
||||
$('#users_group_members').val(uids);
|
||||
function addMember(user, fromUserGroup) {
|
||||
var gravatar = user.icon_link;
|
||||
var username = user.value_display;
|
||||
var userLink = pyroutes.url('edit_user', {"user_id": user.id});
|
||||
var uid = user.id;
|
||||
|
||||
if (fromUserGroup) {
|
||||
username = username +" "+ _gettext('(from usergroup {0})'.format(fromUserGroup))
|
||||
}
|
||||
|
||||
var elem = $(
|
||||
('<tr>'+
|
||||
'<td id="member_user_{6}" class="td-author td-author-new-entry">'+
|
||||
'<div class="group_member">'+
|
||||
'<img class="gravatar" src="{0}" height="16" width="16">'+
|
||||
'<span class="username user"><a href="{1}">{2}</a></span>'+
|
||||
'<input type="hidden" name="__start__" value="member:mapping">'+
|
||||
'<input type="hidden" name="member_user_id" value="{3}">'+
|
||||
'<input type="hidden" name="type" value="new" id="member_{4}">'+
|
||||
'<input type="hidden" name="__end__" value="member:mapping">'+
|
||||
'</div>'+
|
||||
'</td>'+
|
||||
'<td class="td-author-new-entry">'+
|
||||
'<div class="usergroup_member_remove action_button" onclick="removeUserGroupMember({5}, true)" style="visibility: visible;">'+
|
||||
'<i class="icon-remove-sign"></i>'+
|
||||
'</div>'+
|
||||
'</td>'+
|
||||
'</tr>').format(gravatar, userLink, username,
|
||||
uid, uid, uid, uid)
|
||||
);
|
||||
$('#group_members_placeholder').append(elem)
|
||||
}
|
||||
|
||||
if (suggestion.value_type == 'user_group') {
|
||||
|
|
@ -125,20 +169,18 @@
|
|||
pyroutes.url('edit_user_group_members',
|
||||
{'user_group_id': suggestion.id}),
|
||||
function(data) {
|
||||
var uids = [];
|
||||
$.each(data.members, function(idx, user) {
|
||||
var userid = user[0],
|
||||
username = user[1];
|
||||
uids.push(userid.toString());
|
||||
addMember(user, suggestion.value)
|
||||
});
|
||||
preSelectUserIds(uids)
|
||||
}
|
||||
);
|
||||
} else if (suggestion.value_type == 'user') {
|
||||
preSelectUserIds([suggestion.id.toString()]);
|
||||
addMember(suggestion, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -545,12 +545,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
MultiSelectWidget('users_group_members','available_members','edit_users_group');
|
||||
})
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
<div class='field'>
|
||||
|
|
@ -595,12 +589,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
MultiSelectWidget('users_group_members2','available_members','edit_users_group');
|
||||
})
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
<div class='field'>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class TestAdminUsersGroupsController(TestController):
|
|||
def test_index(self):
|
||||
self.log_user()
|
||||
response = self.app.get(url('users_groups'))
|
||||
response.status_int == 200
|
||||
assert response.status_int == 200
|
||||
|
||||
def test_create(self):
|
||||
self.log_user()
|
||||
|
|
@ -148,19 +148,21 @@ class TestAdminUsersGroupsController(TestController):
|
|||
|
||||
fixture.destroy_user_group(users_group_name)
|
||||
|
||||
def test_edit(self):
|
||||
def test_edit_autocomplete(self):
|
||||
self.log_user()
|
||||
ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
|
||||
response = self.app.get(
|
||||
url('edit_users_group', user_group_id=ug.users_group_id))
|
||||
fixture.destroy_user_group(TEST_USER_GROUP)
|
||||
|
||||
def test_edit_user_group_members(self):
|
||||
def test_edit_user_group_autocomplete_members(self, xhr_header):
|
||||
self.log_user()
|
||||
ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
|
||||
response = self.app.get(
|
||||
url('edit_user_group_members', user_group_id=ug.users_group_id))
|
||||
response.mustcontain('No members yet')
|
||||
url('edit_user_group_members', user_group_id=ug.users_group_id),
|
||||
extra_environ=xhr_header)
|
||||
|
||||
assert response.body == '{"members": []}'
|
||||
fixture.destroy_user_group(TEST_USER_GROUP)
|
||||
|
||||
def test_usergroup_escape(self):
|
||||
|
|
@ -181,7 +183,7 @@ class TestAdminUsersGroupsController(TestController):
|
|||
'csrf_token': self.csrf_token
|
||||
}
|
||||
|
||||
response = self.app.post(url('users_groups'), data)
|
||||
self.app.post(url('users_groups'), data)
|
||||
response = self.app.get(url('users_groups'))
|
||||
|
||||
response.mustcontain(
|
||||
|
|
@ -190,3 +192,42 @@ class TestAdminUsersGroupsController(TestController):
|
|||
response.mustcontain(
|
||||
'<img src="/image2" onload="'
|
||||
'alert('Hello, World!');">')
|
||||
|
||||
def test_update_members_from_user_ids(self, user_regular):
|
||||
uid = user_regular.user_id
|
||||
username = user_regular.username
|
||||
self.log_user()
|
||||
|
||||
user_group = fixture.create_user_group('test_gr_ids')
|
||||
assert user_group.members == []
|
||||
assert user_group.user != user_regular
|
||||
expected_active_state = not user_group.users_group_active
|
||||
|
||||
form_data = [
|
||||
('csrf_token', self.csrf_token),
|
||||
('_method', 'put'),
|
||||
('user', username),
|
||||
('users_group_name', 'changed_name'),
|
||||
('users_group_active', expected_active_state),
|
||||
('user_group_description', 'changed_description'),
|
||||
|
||||
('__start__', 'user_group_members:sequence'),
|
||||
('__start__', 'member:mapping'),
|
||||
('member_user_id', uid),
|
||||
('type', 'existing'),
|
||||
('__end__', 'member:mapping'),
|
||||
('__end__', 'user_group_members:sequence'),
|
||||
]
|
||||
ugid = user_group.users_group_id
|
||||
self.app.post(url('update_users_group', user_group_id=ugid), form_data)
|
||||
|
||||
user_group = UserGroup.get(ugid)
|
||||
assert user_group
|
||||
|
||||
assert user_group.members[0].user_id == uid
|
||||
assert user_group.user_id == uid
|
||||
assert 'changed_name' in user_group.users_group_name
|
||||
assert 'changed_description' in user_group.user_group_description
|
||||
assert user_group.users_group_active == expected_active_state
|
||||
|
||||
fixture.destroy_user_group(user_group)
|
||||
|
|
|
|||
|
|
@ -113,40 +113,20 @@ def test_add_and_remove_user_from_group(user_regular, user_util):
|
|||
assert user_group.members == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'data, expected', [
|
||||
("1", [1]), (["1", "2"], [1, 2])
|
||||
]
|
||||
)
|
||||
@pytest.mark.parametrize('data, expected', [
|
||||
([], []),
|
||||
([{"member_user_id": 1, "type": "new"}], [1]),
|
||||
([{"member_user_id": 1, "type": "new"},
|
||||
{"member_user_id": 1, "type": "existing"}], [1]),
|
||||
([{"member_user_id": 1, "type": "new"},
|
||||
{"member_user_id": 2, "type": "new"},
|
||||
{"member_user_id": 3, "type": "remove"}], [1, 2])
|
||||
])
|
||||
def test_clean_members_data(data, expected):
|
||||
cleaned = UserGroupModel()._clean_members_data(data)
|
||||
assert cleaned == expected
|
||||
|
||||
|
||||
def test_update_members_from_user_ids(user_regular, user_util):
|
||||
user_group = user_util.create_user_group()
|
||||
assert user_group.members == []
|
||||
assert user_group.user != user_regular
|
||||
expected_active_state = not user_group.users_group_active
|
||||
|
||||
form_data = {
|
||||
'users_group_members': str(user_regular.user_id),
|
||||
'user': str(user_regular.username),
|
||||
'users_group_name': 'changed_name',
|
||||
'users_group_active': expected_active_state,
|
||||
'user_group_description': 'changed_description'
|
||||
}
|
||||
|
||||
UserGroupModel().update(user_group, form_data)
|
||||
assert user_group.members[0].user_id == user_regular.user_id
|
||||
assert user_group.user_id == user_regular.user_id
|
||||
assert 'changed_name' in user_group.users_group_name
|
||||
assert 'changed_description' in user_group.user_group_description
|
||||
assert user_group.users_group_active == expected_active_state
|
||||
# Ignore changes on the test
|
||||
Session().rollback()
|
||||
|
||||
|
||||
def _create_test_members():
|
||||
members = []
|
||||
for member_number in range(3):
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import requests
|
|||
from webtest.app import TestApp
|
||||
|
||||
import rhodecode
|
||||
from rhodecode.lib.utils2 import AttributeDict
|
||||
from rhodecode.model.changeset_status import ChangesetStatusModel
|
||||
from rhodecode.model.comment import ChangesetCommentsModel
|
||||
from rhodecode.model.db import (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue