##// END OF EJS Templates
admin-users: add view for user groups managment...
Bartłomiej Wołyńczyk -
r1556:9ac012a6 default
parent child Browse files
Show More
@@ -23,7 +23,7 b' import pytest'
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.tests import TEST_USER_REGULAR_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_EMAIL
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29
29
@@ -33,7 +33,8 b' class TestUpdateUserGroup(object):'
33 @pytest.mark.parametrize("changing_attr, updates", [
33 @pytest.mark.parametrize("changing_attr, updates", [
34 ('group_name', {'group_name': 'new_group_name'}),
34 ('group_name', {'group_name': 'new_group_name'}),
35 ('group_name', {'group_name': 'test_group_for_update'}),
35 ('group_name', {'group_name': 'test_group_for_update'}),
36 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
36 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
37 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
37 ('active', {'active': False}),
38 ('active', {'active': False}),
38 ('active', {'active': True})
39 ('active', {'active': True})
39 ])
40 ])
@@ -59,7 +60,8 b' class TestUpdateUserGroup(object):'
59 # TODO: mikhail: decide if we need to test against the commented params
60 # TODO: mikhail: decide if we need to test against the commented params
60 # ('group_name', {'group_name': 'new_group_name'}),
61 # ('group_name', {'group_name': 'new_group_name'}),
61 # ('group_name', {'group_name': 'test_group_for_update'}),
62 # ('group_name', {'group_name': 'test_group_for_update'}),
62 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
63 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
64 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
63 ('active', {'active': False}),
65 ('active', {'active': False}),
64 ('active', {'active': True})
66 ('active', {'active': True})
65 ])
67 ])
@@ -70,6 +70,15 b' def admin_routes(config):'
70 name='edit_user_auth_tokens_delete',
70 name='edit_user_auth_tokens_delete',
71 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
71 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
72
72
73 # user groups management
74 config.add_route(
75 name='edit_user_groups_management',
76 pattern='/users/{user_id:\d+}/edit/groups_management')
77
78 config.add_route(
79 name='edit_user_groups_management_updates',
80 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
81
73
82
74 def includeme(config):
83 def includeme(config):
75 settings = config.get_settings()
84 settings = config.get_settings()
@@ -22,6 +22,7 b' import logging'
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 from rhodecode_tools.lib.ext_json import json
25
26
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib.auth import (
28 from rhodecode.lib.auth import (
@@ -30,6 +31,7 b' from rhodecode.lib import helpers as h'
30 from rhodecode.lib.utils import PartialRenderer
31 from rhodecode.lib.utils import PartialRenderer
31 from rhodecode.lib.utils2 import safe_int, safe_unicode
32 from rhodecode.lib.utils2 import safe_int, safe_unicode
32 from rhodecode.model.auth_token import AuthTokenModel
33 from rhodecode.model.auth_token import AuthTokenModel
34 from rhodecode.model.user_group import UserGroupModel
33 from rhodecode.model.db import User, or_
35 from rhodecode.model.db import User, or_
34 from rhodecode.model.meta import Session
36 from rhodecode.model.meta import Session
35
37
@@ -235,3 +237,49 b' class AdminUsersView(BaseAppView):'
235 h.flash(_("Auth token successfully deleted"), category='success')
237 h.flash(_("Auth token successfully deleted"), category='success')
236
238
237 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
239 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
240
241
242 @LoginRequired()
243 @HasPermissionAllDecorator('hg.admin')
244 @view_config(
245 route_name='edit_user_groups_management', request_method='GET',
246 renderer='rhodecode:templates/admin/users/user_edit.mako')
247 def groups_management(self):
248 c = self.load_default_context()
249
250 user_id = self.request.matchdict.get('user_id')
251 c.user = User.get_or_404(user_id, pyramid_exc=True)
252 c.data = c.user.group_member
253 self._redirect_for_default_user(c.user.username)
254 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group) for group in c.user.group_member]
255 c.groups = json.dumps(groups)
256 c.active = 'groups'
257
258 return self._get_template_context(c)
259
260
261 @LoginRequired()
262 @HasPermissionAllDecorator('hg.admin')
263 @view_config(
264 route_name='edit_user_groups_management_updates', request_method='POST')
265 def groups_management_updates(self):
266 _ = self.request.translate
267 c = self.load_default_context()
268
269 user_id = self.request.matchdict.get('user_id')
270 c.user = User.get_or_404(user_id, pyramid_exc=True)
271 self._redirect_for_default_user(c.user.username)
272
273 users_groups = set(self.request.POST.getall('users_group_id'))
274 users_groups_model = []
275
276 for ugid in users_groups:
277 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
278 user_group_model = UserGroupModel()
279 user_group_model.change_groups(c.user, users_groups_model)
280
281 Session().commit()
282 c.active = 'user_groups_management'
283 h.flash(_("Groups successfully changed"), category='success')
284
285 return HTTPFound(h.route_path('edit_user_groups_management', user_id=user_id))
@@ -1261,14 +1261,15 b' class UserGroup(Base, BaseModel):'
1261
1261
1262 """
1262 """
1263 user_group = self
1263 user_group = self
1264
1265 data = {
1264 data = {
1266 'users_group_id': user_group.users_group_id,
1265 'users_group_id': user_group.users_group_id,
1267 'group_name': user_group.users_group_name,
1266 'group_name': user_group.users_group_name,
1268 'group_description': user_group.user_group_description,
1267 'group_description': user_group.user_group_description,
1269 'active': user_group.users_group_active,
1268 'active': user_group.users_group_active,
1270 'owner': user_group.user.username,
1269 'owner': user_group.user.username,
1270 'owner_email': user_group.user.email,
1271 }
1271 }
1272
1272 if with_group_members:
1273 if with_group_members:
1273 users = []
1274 users = []
1274 for user in user_group.members:
1275 for user in user_group.members:
@@ -197,6 +197,7 b' class RepoModel(BaseModel):'
197 return _users
197 return _users
198
198
199 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
199 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
200
200 # TODO: mikhail: move this method to the UserGroupModel.
201 # TODO: mikhail: move this method to the UserGroupModel.
201 query = self.sa.query(UserGroup)
202 query = self.sa.query(UserGroup)
202 if only_active:
203 if only_active:
@@ -223,6 +224,12 b' class RepoModel(BaseModel):'
223 'value_display': 'Group: %s (%d members)' % (
224 'value_display': 'Group: %s (%d members)' % (
224 group.users_group_name, len(group.members),),
225 group.users_group_name, len(group.members),),
225 'value': group.users_group_name,
226 'value': group.users_group_name,
227 'description': group.user_group_description,
228 'owner': group.user.username,
229
230 'owner_icon': h.gravatar_url(group.user.email, 30),
231 'value_display_owner': h.person(group.user.email),
232
226 'value_type': 'user_group',
233 'value_type': 'user_group',
227 'active': group.users_group_active,
234 'active': group.users_group_active,
228 }
235 }
@@ -512,3 +512,49 b' class UserGroupModel(BaseModel):'
512 else:
512 else:
513 log.debug('Skipping addition to group %s since it is '
513 log.debug('Skipping addition to group %s since it is '
514 'not managed by auth plugins' % gr)
514 'not managed by auth plugins' % gr)
515
516
517 def change_groups(self, user, groups):
518 """
519 This method changes user group assignment
520 :param user: User
521 :param groups: array of UserGroupModel
522 :return:
523 """
524 user = self._get_user(user)
525 log.debug('Changing user(%s) assignment to groups(%s)', user, groups)
526 current_groups = user.group_member
527 current_groups = [x.users_group for x in current_groups]
528
529 # calculate from what groups user should be removed/add
530 groups = set(groups)
531 current_groups = set(current_groups)
532
533 groups_to_remove = current_groups - groups
534 groups_to_add = groups - current_groups
535
536 for gr in groups_to_remove:
537 log.debug('Removing user %s from user group %s', user.username, gr.users_group_name)
538 self.remove_user_from_group(gr.users_group_name, user.username)
539 for gr in groups_to_add:
540 log.debug('Adding user %s to user group %s', user.username, gr.users_group_name)
541 UserGroupModel().add_user_to_group(gr.users_group_name, user.username)
542
543 @staticmethod
544 def get_user_groups_as_dict(user_group):
545 import rhodecode.lib.helpers as h
546
547 data = {
548 'users_group_id': user_group.users_group_id,
549 'group_name': user_group.users_group_name,
550 'group_description': user_group.user_group_description,
551 'active': user_group.users_group_active,
552 "owner": user_group.user.username,
553 'owner_icon': h.gravatar_url(user_group.user.email, 30),
554 "owner_data": {'owner': user_group.user.username, 'owner_icon': h.gravatar_url(user_group.user.email, 30)}
555 }
556 return data
557
558
559
560
@@ -37,6 +37,11 b''
37 <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>
37 <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>
38 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
38 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.url('edit_user_emails', user_id=c.user.user_id)}">${_('Emails')}</a></li>
39 <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>
39 <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>
40
41 <li class="${'active' if c.active=='groups' else ''}">
42 <a href="${h.route_path('edit_user_groups_management', user_id=c.user.user_id)}">${_('User Groups Management')}</a>
43 </li>
44
40 </ul>
45 </ul>
41 </div>
46 </div>
42
47
@@ -1,82 +1,92 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
3
2
4 <%def name="title()">
5 ${_('User groups administration')}
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
9 </%def>
10
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
14 </%def>
15
16 <%def name="menu_bar_nav()">
17 ${self.menu_items(active='admin')}
18 </%def>
19
3
20 <%def name="main()">
4 <div class="panel panel-default">
21 <div class="box">
5 <div class="panel-heading">
6 <h3 class="panel-title">${_('User groups administration')}</h3>
7 </div>
8 <div class="panel-body">
9 <div class="field">
10 <div class="label label-checkbox">
11 <label for="users_group_active">${_('Add user to group')}:</label>
12 </div>
13 <div class="input">
14 ${h.text('add_user_to_group', placeholder="user group name", class_="medium")}
15 </div>
22
16
23 <div class="title">
17 </div>
24 ${self.breadcrumbs()}
25 <ul class="links">
26 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
27 <li>
28 <a href="${h.url('new_users_group')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
29 </li>
30 %endif
31 </ul>
32 </div>
33
18
34 <div id="repos_list_wrap">
19 <div class="groups_management">
35 <table id="user_group_list_table" class="display"></table>
20 ${h.secure_form(h.route_path('edit_user_groups_management_updates', user_id=c.user.user_id), method='post')}
36 </div>
21 <div id="repos_list_wrap">
37
22 <table id="user_group_list_table" class="display"></table>
23 </div>
24 <div class="buttons">
25 ${h.submit('save',_('Save'),class_="btn")}
26 </div>
27 ${h.end_form()}
28 </div>
29 </div>
38 </div>
30 </div>
39 <script>
31 <script>
32 var api;
40 $(document).ready(function() {
33 $(document).ready(function() {
41
34
42 var get_datatable_count = function(){
35 var get_datatable_count = function(){
43 var api = $('#user_group_list_table').dataTable().api();
36 $('#user_group_count').text(api.page.info().recordsDisplay);
44 $('#user_group_count').text(api.page.info().recordsDisplay);
45 };
37 };
46
38
47 // user list
39 $('#user_group_list_table').on('click', 'a.editor_remove', function (e) {
40 e.preventDefault();
41 var row = api.row($(this).closest('tr'));
42 row.remove().draw();
43 } );
44
48 $('#user_group_list_table').DataTable({
45 $('#user_group_list_table').DataTable({
49 data: ${c.data|n},
46 data: ${c.groups|n},
50 dom: 'rtp',
47 dom: 'rtp',
51 pageLength: ${c.visual.admin_grid_items},
48 pageLength: ${c.visual.admin_grid_items},
52 order: [[ 0, "asc" ]],
49 order: [[ 0, "asc" ]],
53 columns: [
50 columns: [
54 { data: {"_": "group_name",
51 { data: {"_": "group_name",
55 "sort": "group_name_raw"}, title: "${_('Name')}", className: "td-componentname" },
52 "sort": "group_name"}, title: "${_('Name')}", className: "td-componentname," ,
56 { data: {"_": "desc",
53 render: function (data,type,full,meta)
57 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
54 {return '<div><i class="icon-group" title="User group">'+data+'</i></div>'}},
58 { data: {"_": "members",
55
59 "sort": "members",
56 { data: {"_": "group_description",
60 "type": Number}, title: "${_('Members')}", className: "td-number" },
57 "sort": "group_description"}, title: "${_('Description')}", className: "td-description" },
58 { data: {"_": "users_group_id"}, className: "td-user",
59 render: function (data,type,full,meta)
60 {return '<input type="hidden" name="users_group_id" value="'+data+'">'}},
61 { data: {"_": "active",
61 { data: {"_": "active",
62 "sort": "active"}, title: "${_('Active')}", className: "td-active", className: "td-number"},
62 "sort": "active"}, title: "${_('Active')}", className: "td-active", className: "td-number"},
63 { data: {"_": "owner",
63 { data: {"_": "owner_data"}, title: "${_('Owner')}", className: "td-user",
64 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
64 render: function (data,type,full,meta)
65 { data: {"_": "action",
65 {return '<div class="rc-user tooltip">'+
66 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
66 '<img class="gravatar" src="'+ data.owner_icon +'" height="16" width="16">'+
67 data.owner +'</div>'
68 }
69 },
70 { data: null,
71 title: "${_('Action')}",
72 className: "td-action",
73 defaultContent: '<a href="" class="btn btn-link btn-danger">Delete</a>'
74 },
67 ],
75 ],
68 language: {
76 language: {
69 paginate: DEFAULT_GRID_PAGINATION,
77 paginate: DEFAULT_GRID_PAGINATION,
70 emptyTable: _gettext("No user groups available yet.")
78 emptyTable: _gettext("No user groups available yet.")
71 },
79 },
72 "initComplete": function( settings, json ) {
80 "initComplete": function( settings, json ) {
81 var data_grid = $('#user_group_list_table').dataTable();
82 api = data_grid.api();
73 get_datatable_count();
83 get_datatable_count();
74 }
84 }
75 });
85 });
76
86
77 // update the counter when doing search
87 // update the counter when doing search
78 $('#user_group_list_table').on( 'search.dt', function (e,settings) {
88 $('#user_group_list_table').on( 'search.dt', function (e,settings) {
79 get_datatable_count();
89 get_datatable_count();
80 });
90 });
81
91
82 // filter, filter both grids
92 // filter, filter both grids
@@ -93,6 +103,43 b''
93
103
94 });
104 });
95
105
106 $('#language').select2({
107 'containerCssClass': "drop-menu",
108 'dropdownCssClass': "drop-menu-dropdown",
109 'dropdownAutoWidth': true
110 });
111
112
113
114 $(document).ready(function(){
115 $("#group_parent_id").select2({
116 'containerCssClass': "drop-menu",
117 'dropdownCssClass': "drop-menu-dropdown",
118 'dropdownAutoWidth': true
119 });
120
121 $('#add_user_to_group').autocomplete({
122 serviceUrl: pyroutes.url('user_group_autocomplete_data'),
123 minChars:2,
124 maxHeight:400,
125 width:300,
126 deferRequestBy: 300, //miliseconds
127 showNoSuggestionNotice: true,
128 params: { user_groups:true },
129 formatResult: autocompleteFormatResult,
130 lookupFilter: autocompleteFilterResult,
131 onSelect: function(element, suggestion){
132 var owner = {owner_icon: suggestion.owner_icon, owner:suggestion.owner};
133 api.row.add(
134 {"active": suggestion.active,
135 "owner_data": owner,
136 "users_group_id": suggestion.id,
137 "group_description": suggestion.description,
138 "group_name": suggestion.value}).draw();
139 }
140 });
141 })
142
96 </script>
143 </script>
97
144
98 </%def>
145
@@ -260,8 +260,9 b' class TestGetUserGroups(object):'
260 user_util.create_user_group(users_group_active=True))
260 user_util.create_user_group(users_group_active=True))
261
261
262 group_filter = created_groups[-1].users_group_name[-2:]
262 group_filter = created_groups[-1].users_group_name[-2:]
263 with self._patch_user_group_list():
263 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
264 groups = RepoModel().get_user_groups(group_filter)
264 with self._patch_user_group_list():
265 groups = RepoModel().get_user_groups(group_filter)
265
266
266 fake_groups = [
267 fake_groups = [
267 u for u in groups if u['value'].startswith('test_returns')]
268 u for u in groups if u['value'].startswith('test_returns')]
@@ -275,9 +276,9 b' class TestGetUserGroups(object):'
275 for i in range(3):
276 for i in range(3):
276 created_groups.append(
277 created_groups.append(
277 user_util.create_user_group(users_group_active=True))
278 user_util.create_user_group(users_group_active=True))
278
279 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
279 with self._patch_user_group_list():
280 with self._patch_user_group_list():
280 groups = RepoModel().get_user_groups('test_returns')
281 groups = RepoModel().get_user_groups('test_returns')
281
282
282 fake_groups = [
283 fake_groups = [
283 u for u in groups if u['value'].startswith('test_returns')]
284 u for u in groups if u['value'].startswith('test_returns')]
@@ -287,9 +288,9 b' class TestGetUserGroups(object):'
287 for i in range(4):
288 for i in range(4):
288 is_active = i % 2 == 0
289 is_active = i % 2 == 0
289 user_util.create_user_group(users_group_active=is_active)
290 user_util.create_user_group(users_group_active=is_active)
290
291 with mock.patch('rhodecode.lib.helpers.gravatar_url'):
291 with self._patch_user_group_list():
292 with self._patch_user_group_list():
292 groups = RepoModel().get_user_groups()
293 groups = RepoModel().get_user_groups()
293 expected = ('id', 'icon_link', 'value_display', 'value', 'value_type')
294 expected = ('id', 'icon_link', 'value_display', 'value', 'value_type')
294 for group in groups:
295 for group in groups:
295 assert group['value_type'] is 'user_group'
296 assert group['value_type'] is 'user_group'
General Comments 0
You need to be logged in to leave comments. Login now