Show More
@@ -23,7 +23,7 b' import pytest' | |||
|
23 | 23 | |
|
24 | 24 | from rhodecode.model.user import UserModel |
|
25 | 25 | from rhodecode.model.user_group import UserGroupModel |
|
26 |
from rhodecode.tests import TEST_USER_ |
|
|
26 | from rhodecode.tests import TEST_USER_ADMIN_EMAIL | |
|
27 | 27 | from rhodecode.api.tests.utils import ( |
|
28 | 28 | build_data, api_call, assert_error, assert_ok, crash, jsonify) |
|
29 | 29 | |
@@ -33,7 +33,8 b' class TestUpdateUserGroup(object):' | |||
|
33 | 33 | @pytest.mark.parametrize("changing_attr, updates", [ |
|
34 | 34 | ('group_name', {'group_name': 'new_group_name'}), |
|
35 | 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 | 38 | ('active', {'active': False}), |
|
38 | 39 | ('active', {'active': True}) |
|
39 | 40 | ]) |
@@ -59,7 +60,8 b' class TestUpdateUserGroup(object):' | |||
|
59 | 60 | # TODO: mikhail: decide if we need to test against the commented params |
|
60 | 61 | # ('group_name', {'group_name': 'new_group_name'}), |
|
61 | 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 | 65 | ('active', {'active': False}), |
|
64 | 66 | ('active', {'active': True}) |
|
65 | 67 | ]) |
@@ -70,6 +70,15 b' def admin_routes(config):' | |||
|
70 | 70 | name='edit_user_auth_tokens_delete', |
|
71 | 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 | 83 | def includeme(config): |
|
75 | 84 | settings = config.get_settings() |
@@ -22,6 +22,7 b' import logging' | |||
|
22 | 22 | |
|
23 | 23 | from pyramid.httpexceptions import HTTPFound |
|
24 | 24 | from pyramid.view import view_config |
|
25 | from rhodecode_tools.lib.ext_json import json | |
|
25 | 26 | |
|
26 | 27 | from rhodecode.apps._base import BaseAppView |
|
27 | 28 | from rhodecode.lib.auth import ( |
@@ -30,6 +31,7 b' from rhodecode.lib import helpers as h' | |||
|
30 | 31 | from rhodecode.lib.utils import PartialRenderer |
|
31 | 32 | from rhodecode.lib.utils2 import safe_int, safe_unicode |
|
32 | 33 | from rhodecode.model.auth_token import AuthTokenModel |
|
34 | from rhodecode.model.user_group import UserGroupModel | |
|
33 | 35 | from rhodecode.model.db import User, or_ |
|
34 | 36 | from rhodecode.model.meta import Session |
|
35 | 37 | |
@@ -235,3 +237,49 b' class AdminUsersView(BaseAppView):' | |||
|
235 | 237 | h.flash(_("Auth token successfully deleted"), category='success') |
|
236 | 238 | |
|
237 | 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 | 1263 | user_group = self |
|
1264 | ||
|
1265 | 1264 | data = { |
|
1266 | 1265 | 'users_group_id': user_group.users_group_id, |
|
1267 | 1266 | 'group_name': user_group.users_group_name, |
|
1268 | 1267 | 'group_description': user_group.user_group_description, |
|
1269 | 1268 | 'active': user_group.users_group_active, |
|
1270 | 1269 | 'owner': user_group.user.username, |
|
1270 | 'owner_email': user_group.user.email, | |
|
1271 | 1271 | } |
|
1272 | ||
|
1272 | 1273 | if with_group_members: |
|
1273 | 1274 | users = [] |
|
1274 | 1275 | for user in user_group.members: |
@@ -197,6 +197,7 b' class RepoModel(BaseModel):' | |||
|
197 | 197 | return _users |
|
198 | 198 | |
|
199 | 199 | def get_user_groups(self, name_contains=None, limit=20, only_active=True): |
|
200 | ||
|
200 | 201 | # TODO: mikhail: move this method to the UserGroupModel. |
|
201 | 202 | query = self.sa.query(UserGroup) |
|
202 | 203 | if only_active: |
@@ -223,6 +224,12 b' class RepoModel(BaseModel):' | |||
|
223 | 224 | 'value_display': 'Group: %s (%d members)' % ( |
|
224 | 225 | group.users_group_name, len(group.members),), |
|
225 | 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 | 233 | 'value_type': 'user_group', |
|
227 | 234 | 'active': group.users_group_active, |
|
228 | 235 | } |
@@ -512,3 +512,49 b' class UserGroupModel(BaseModel):' | |||
|
512 | 512 | else: |
|
513 | 513 | log.debug('Skipping addition to group %s since it is ' |
|
514 | 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 | 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 | 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 | 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 | 45 | </ul> |
|
41 | 46 | </div> |
|
42 | 47 |
@@ -1,82 +1,92 b'' | |||
|
1 | 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 | · ${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'))} » <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()"> | |
|
21 | <div class="box"> | |
|
4 | <div class="panel panel-default"> | |
|
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"> | |
|
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> | |
|
17 | </div> | |
|
33 | 18 | |
|
34 | <div id="repos_list_wrap"> | |
|
35 | <table id="user_group_list_table" class="display"></table> | |
|
36 | </div> | |
|
37 | ||
|
19 | <div class="groups_management"> | |
|
20 | ${h.secure_form(h.route_path('edit_user_groups_management_updates', user_id=c.user.user_id), method='post')} | |
|
21 | <div id="repos_list_wrap"> | |
|
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 | 30 | </div> |
|
39 | 31 | <script> |
|
32 | var api; | |
|
40 | 33 | $(document).ready(function() { |
|
41 | 34 | |
|
42 | 35 | var get_datatable_count = function(){ |
|
43 | var api = $('#user_group_list_table').dataTable().api(); | |
|
44 | $('#user_group_count').text(api.page.info().recordsDisplay); | |
|
36 | $('#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 | 45 | $('#user_group_list_table').DataTable({ |
|
49 |
data: ${c. |
|
|
46 | data: ${c.groups|n}, | |
|
50 | 47 | dom: 'rtp', |
|
51 | 48 | pageLength: ${c.visual.admin_grid_items}, |
|
52 | 49 | order: [[ 0, "asc" ]], |
|
53 | 50 | columns: [ |
|
54 | 51 | { data: {"_": "group_name", |
|
55 |
"sort": "group_name |
|
|
56 | { data: {"_": "desc", | |
|
57 | "sort": "desc"}, title: "${_('Description')}", className: "td-description" }, | |
|
58 | { data: {"_": "members", | |
|
59 | "sort": "members", | |
|
60 |
"t |
|
|
52 | "sort": "group_name"}, title: "${_('Name')}", className: "td-componentname," , | |
|
53 | render: function (data,type,full,meta) | |
|
54 | {return '<div><i class="icon-group" title="User group">'+data+'</i></div>'}}, | |
|
55 | ||
|
56 | { data: {"_": "group_description", | |
|
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 | 61 | { data: {"_": "active", |
|
62 | 62 | "sort": "active"}, title: "${_('Active')}", className: "td-active", className: "td-number"}, |
|
63 | { data: {"_": "owner", | |
|
64 | "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }, | |
|
65 | { data: {"_": "action", | |
|
66 | "sort": "action"}, title: "${_('Action')}", className: "td-action" } | |
|
63 | { data: {"_": "owner_data"}, title: "${_('Owner')}", className: "td-user", | |
|
64 | render: function (data,type,full,meta) | |
|
65 | {return '<div class="rc-user tooltip">'+ | |
|
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 | 76 | language: { |
|
69 | 77 | paginate: DEFAULT_GRID_PAGINATION, |
|
70 | 78 | emptyTable: _gettext("No user groups available yet.") |
|
71 | 79 | }, |
|
72 | 80 | "initComplete": function( settings, json ) { |
|
81 | var data_grid = $('#user_group_list_table').dataTable(); | |
|
82 | api = data_grid.api(); | |
|
73 | 83 | get_datatable_count(); |
|
74 | 84 | } |
|
75 | 85 | }); |
|
76 | 86 | |
|
77 | 87 | // update the counter when doing search |
|
78 | 88 | $('#user_group_list_table').on( 'search.dt', function (e,settings) { |
|
79 | get_datatable_count(); | |
|
89 | get_datatable_count(); | |
|
80 | 90 | }); |
|
81 | 91 | |
|
82 | 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 | 143 | </script> |
|
97 | 144 | |
|
98 | </%def> | |
|
145 |
@@ -260,8 +260,9 b' class TestGetUserGroups(object):' | |||
|
260 | 260 | user_util.create_user_group(users_group_active=True)) |
|
261 | 261 | |
|
262 | 262 | group_filter = created_groups[-1].users_group_name[-2:] |
|
263 | with self._patch_user_group_list(): | |
|
264 | groups = RepoModel().get_user_groups(group_filter) | |
|
263 | with mock.patch('rhodecode.lib.helpers.gravatar_url'): | |
|
264 | with self._patch_user_group_list(): | |
|
265 | groups = RepoModel().get_user_groups(group_filter) | |
|
265 | 266 | |
|
266 | 267 | fake_groups = [ |
|
267 | 268 | u for u in groups if u['value'].startswith('test_returns')] |
@@ -275,9 +276,9 b' class TestGetUserGroups(object):' | |||
|
275 | 276 | for i in range(3): |
|
276 | 277 | created_groups.append( |
|
277 | 278 | user_util.create_user_group(users_group_active=True)) |
|
278 | ||
|
279 | with self._patch_user_group_list(): | |
|
280 | groups = RepoModel().get_user_groups('test_returns') | |
|
279 | with mock.patch('rhodecode.lib.helpers.gravatar_url'): | |
|
280 | with self._patch_user_group_list(): | |
|
281 | groups = RepoModel().get_user_groups('test_returns') | |
|
281 | 282 | |
|
282 | 283 | fake_groups = [ |
|
283 | 284 | u for u in groups if u['value'].startswith('test_returns')] |
@@ -287,9 +288,9 b' class TestGetUserGroups(object):' | |||
|
287 | 288 | for i in range(4): |
|
288 | 289 | is_active = i % 2 == 0 |
|
289 | 290 | user_util.create_user_group(users_group_active=is_active) |
|
290 | ||
|
291 | with self._patch_user_group_list(): | |
|
292 | groups = RepoModel().get_user_groups() | |
|
291 | with mock.patch('rhodecode.lib.helpers.gravatar_url'): | |
|
292 | with self._patch_user_group_list(): | |
|
293 | groups = RepoModel().get_user_groups() | |
|
293 | 294 | expected = ('id', 'icon_link', 'value_display', 'value', 'value_type') |
|
294 | 295 | for group in groups: |
|
295 | 296 | assert group['value_type'] is 'user_group' |
General Comments 0
You need to be logged in to leave comments.
Login now