Merge "Refactoring of user assignment workflow."
This commit is contained in:
commit
79f503d477
484
horizon/static/horizon/js/horizon.membership.js
Normal file
484
horizon/static/horizon/js/horizon.membership.js
Normal file
@ -0,0 +1,484 @@
|
||||
/* Namespace for core functionality related to Membership Workflow Step. */
|
||||
horizon.membership = {
|
||||
|
||||
current_membership: [],
|
||||
data: [],
|
||||
roles: [],
|
||||
has_roles: [],
|
||||
default_role_id: [],
|
||||
|
||||
/* Parses the form field selector's ID to get either the
|
||||
* role or user id (i.e. returns "id12345" when
|
||||
* passed the selector with id: "id_group_id12345").
|
||||
**/
|
||||
get_field_id: function(id_string) {
|
||||
return id_string.slice(id_string.lastIndexOf("_") + 1);
|
||||
},
|
||||
|
||||
/*
|
||||
* Gets the html select element associated with a given
|
||||
* role id.
|
||||
**/
|
||||
get_role_element: function(step_slug, role_id) {
|
||||
return $('select[id^="id_' + step_slug + '_role_' + role_id + '"]');
|
||||
},
|
||||
|
||||
/*
|
||||
* Gets the html ul element associated with a given
|
||||
* data id. I.e., the member's row.
|
||||
**/
|
||||
get_member_element: function(step_slug, data_id) {
|
||||
return $('li[data-' + step_slug + '-id$=' + data_id + ']').parent();
|
||||
},
|
||||
|
||||
/*
|
||||
* Initializes all of the horizon.membership lists with
|
||||
* data parsed from the hidden form fields, as well as the
|
||||
* default role id.
|
||||
**/
|
||||
init_properties: function(step_slug) {
|
||||
horizon.membership.has_roles[step_slug] = $("." + step_slug + "_membership").data('show-roles') !== "no";
|
||||
horizon.membership.default_role_id[step_slug] = $('#id_default_' + step_slug + '_role').attr('value');
|
||||
horizon.membership.init_data_list(step_slug);
|
||||
horizon.membership.init_role_list(step_slug);
|
||||
horizon.membership.init_current_membership(step_slug);
|
||||
},
|
||||
|
||||
/*
|
||||
* Initializes an associative array mapping data ids to display names.
|
||||
**/
|
||||
init_data_list: function(step_slug) {
|
||||
horizon.membership.data[step_slug] = [];
|
||||
_.each($(this.get_role_element(step_slug, "")).find("option"), function (option) {
|
||||
horizon.membership.data[step_slug][option.value] = option.text;
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Initializes an associative array mapping role ids to role names.
|
||||
**/
|
||||
init_role_list: function(step_slug) {
|
||||
horizon.membership.roles[step_slug] = [];
|
||||
_.each($('label[for^="id_' + step_slug + '_role_"]'), function(role) {
|
||||
var id = horizon.membership.get_field_id($(role).attr('for'));
|
||||
horizon.membership.roles[step_slug][id] = $(role).text();
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Initializes an associative array of lists of the current
|
||||
* members for each available role.
|
||||
**/
|
||||
init_current_membership: function(step_slug) {
|
||||
horizon.membership.current_membership[step_slug] = [];
|
||||
var members_list = [];
|
||||
var role_name, role_id, selected_members;
|
||||
_.each(this.get_role_element(step_slug, ''), function(value, key) {
|
||||
role_id = horizon.membership.get_field_id($(value).attr('id'));
|
||||
role_name = $('label[for="id_' + step_slug + '_role_' + role_id + '"]').text();
|
||||
|
||||
// get the array of members who are selected in this list
|
||||
selected_members = $(value).find("option:selected");
|
||||
// extract the member names and add them to the dictionary of lists
|
||||
members_list = [];
|
||||
if (selected_members) {
|
||||
_.each(selected_members, function(member) {
|
||||
members_list.push(member.value);
|
||||
});
|
||||
}
|
||||
horizon.membership.current_membership[step_slug][role_id] = members_list;
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns the ids of roles the data is member of.
|
||||
**/
|
||||
get_member_roles: function(step_slug, data_id) {
|
||||
var roles = [];
|
||||
for (var role in horizon.membership.current_membership[step_slug]) {
|
||||
if ($.inArray(data_id, horizon.membership.current_membership[step_slug][role]) >= 0) {
|
||||
roles.push(role);
|
||||
}
|
||||
}
|
||||
return roles;
|
||||
},
|
||||
|
||||
/*
|
||||
* Updates the selected values on the role_list's form field, as
|
||||
* well as the current_membership dictionary's list.
|
||||
**/
|
||||
update_role_lists: function(step_slug, role_id, new_list) {
|
||||
this.get_role_element(step_slug, role_id).val(new_list);
|
||||
horizon.membership.current_membership[step_slug][role_id] = new_list;
|
||||
},
|
||||
|
||||
/*
|
||||
* Helper function for remove_member_from_role.
|
||||
**/
|
||||
remove_member: function(step_slug, data_id, role_id, role_list) {
|
||||
var index = role_list.indexOf(data_id);
|
||||
if (index >= 0) {
|
||||
// remove member from list
|
||||
role_list.splice(index, 1);
|
||||
horizon.membership.update_role_lists(step_slug, role_id, role_list);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Searches through the role lists and removes a given member
|
||||
* from the lists.
|
||||
**/
|
||||
remove_member_from_role: function(step_slug, data_id, role_id) {
|
||||
var role_list;
|
||||
if (role_id) {
|
||||
role_list = horizon.membership.current_membership[step_slug][role_id];
|
||||
horizon.membership.remove_member(step_slug, data_id, role_id, role_list);
|
||||
}
|
||||
else {
|
||||
// search for membership in role lists
|
||||
for (var role in horizon.membership.current_membership[step_slug]) {
|
||||
role_list = horizon.membership.current_membership[step_slug][role];
|
||||
horizon.membership.remove_member(step_slug, data_id, role, role_list);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Adds a member to a given role list.
|
||||
**/
|
||||
add_member_to_role: function(step_slug, data_id, role_id) {
|
||||
var role_list = horizon.membership.current_membership[step_slug][role_id];
|
||||
role_list.push(data_id);
|
||||
horizon.membership.update_role_lists(step_slug, role_id, role_list);
|
||||
},
|
||||
|
||||
update_member_role_dropdown: function(step_slug, data_id, role_ids, member_el) {
|
||||
if (typeof(role_ids) === 'undefined') {
|
||||
role_ids = horizon.membership.get_member_roles(step_slug, data_id);
|
||||
}
|
||||
if (typeof(member_el) === 'undefined') {
|
||||
member_el = horizon.membership.get_member_element(step_slug, data_id);
|
||||
}
|
||||
|
||||
var $dropdown = member_el.find("li.member").siblings('.dropdown');
|
||||
var $role_items = $dropdown.children('.role_dropdown').children('li');
|
||||
|
||||
$role_items.each(function (idx, el) {
|
||||
if (_.contains(role_ids, $(el).data('role-id'))) {
|
||||
$(el).addClass('selected');
|
||||
} else {
|
||||
$(el).removeClass('selected');
|
||||
}
|
||||
});
|
||||
|
||||
// set the selection back to default role
|
||||
var $roles_display = $dropdown.children('.dropdown-toggle').children('.roles_display');
|
||||
var roles_to_display = [];
|
||||
for (var i = 0; i < role_ids.length; i++) {
|
||||
if (i == 2) {
|
||||
roles_to_display.push('...');
|
||||
break;
|
||||
}
|
||||
roles_to_display.push(horizon.membership.roles[step_slug][role_ids[i]]);
|
||||
}
|
||||
text = roles_to_display.join(', ');
|
||||
if (text.length == 0) text = gettext('No roles');
|
||||
$roles_display.text(text);
|
||||
},
|
||||
|
||||
/*
|
||||
* Generates the HTML structure for a member that will be displayed
|
||||
* as a list item in the member list.
|
||||
**/
|
||||
generate_member_element: function(step_slug, display_name, data_id, role_ids, text) {
|
||||
var str_id = "id_" + step_slug + "_" + data_id;
|
||||
|
||||
var roles = [];
|
||||
for (var r in horizon.membership.roles[step_slug]) {
|
||||
var role = {};
|
||||
role['role_id'] = r;
|
||||
role['role_name'] = horizon.membership.roles[step_slug][r];
|
||||
roles.push(role);
|
||||
}
|
||||
|
||||
var template = horizon.templates.compiled_templates["#membership_template"],
|
||||
params = {data_id: str_id,
|
||||
step_slug: step_slug,
|
||||
default_role: horizon.membership.roles[horizon.membership.default_role_id[step_slug]],
|
||||
display_name: display_name,
|
||||
text: text,
|
||||
roles: roles},
|
||||
member_el = $(template.render(params));
|
||||
this.update_member_role_dropdown(step_slug, str_id, role_ids, member_el);
|
||||
return $(member_el);
|
||||
},
|
||||
|
||||
/*
|
||||
* Generates the HTML structure for the membership UI.
|
||||
**/
|
||||
generate_html: function(step_slug) {
|
||||
var data;
|
||||
for (data in horizon.membership.data[step_slug]) {
|
||||
var data_id = data;
|
||||
var display_name = horizon.membership.data[step_slug][data_id];
|
||||
var role_ids = this.get_member_roles(step_slug, data_id);
|
||||
if (role_ids.length > 0) {
|
||||
$("." + step_slug + "_members").append(this.generate_member_element(step_slug, display_name, data_id, role_ids, "-"));
|
||||
}
|
||||
else {
|
||||
$(".available_" + step_slug).append(this.generate_member_element(step_slug, display_name, data_id, role_ids, "+"));
|
||||
}
|
||||
}
|
||||
horizon.membership.detect_no_results(step_slug);
|
||||
},
|
||||
|
||||
/*
|
||||
* Triggers on click of link to add/remove membership association.
|
||||
**/
|
||||
update_membership: function(step_slug) {
|
||||
$(".available_" + step_slug + ", ." + step_slug + "_members").on('click', ".btn-group a[href='#add_remove']", function (evt) {
|
||||
evt.preventDefault();
|
||||
var available = $(".available_" + step_slug).has($(this)).length;
|
||||
var data_id = horizon.membership.get_field_id($(this).parent().siblings().attr('data-' + step_slug + '-id'));
|
||||
var member_el = $(this).parent().parent();
|
||||
|
||||
if (available) {
|
||||
var default_role = horizon.membership.default_role_id[step_slug];
|
||||
$(this).text("-");
|
||||
$("." + step_slug + "_members").append(member_el);
|
||||
horizon.membership.add_member_to_role(step_slug, data_id, default_role);
|
||||
|
||||
if (horizon.membership.has_roles[step_slug]) {
|
||||
$(this).parent().siblings(".role_options").show();
|
||||
horizon.membership.update_member_role_dropdown(step_slug, data_id, [default_role], member_el);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$(this).text("+");
|
||||
$(this).parent().siblings(".role_options").hide();
|
||||
$(".available_" + step_slug).append(member_el);
|
||||
horizon.membership.remove_member_from_role(step_slug, data_id);
|
||||
}
|
||||
|
||||
// update lists
|
||||
horizon.membership.list_filtering(step_slug);
|
||||
horizon.membership.detect_no_results(step_slug);
|
||||
|
||||
// remove input filters
|
||||
$("input." + step_slug + "_filter").val("");
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Detects whether each list has members and if it does not
|
||||
* displays a message to the user.
|
||||
**/
|
||||
detect_no_results: function (step_slug) {
|
||||
$('.' + step_slug + '_filterable').each( function () {
|
||||
var css_class = $(this).find('ul').attr('class');
|
||||
// Example value: members step_slug_members
|
||||
// Pick the class name that contains the step_slug
|
||||
var filter = _.find(css_class.split(' '), function(val){ return val.indexOf(step_slug) != -1; });
|
||||
|
||||
if (!$('.' + filter).children('ul').length) {
|
||||
$('#no_' + filter).show();
|
||||
$("input[id='" + filter + "']").attr('disabled', 'disabled');
|
||||
}
|
||||
else {
|
||||
$('#no_' + filter).hide();
|
||||
$("input[id='" + filter + "']").removeAttr('disabled');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Triggers on selection of new role for a member.
|
||||
**/
|
||||
select_member_role: function(step_slug) {
|
||||
$(".available_" + step_slug + ", ." + step_slug + "_members").on('click', '.role_dropdown li', function (evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
// get the newly selected role and the member's name
|
||||
var new_role_id = $(this).attr("data-role-id");
|
||||
var id_str = $(this).parent().parent().siblings(".member").attr("data-" + step_slug + "-id");
|
||||
var data_id = horizon.membership.get_field_id(id_str);
|
||||
// update role lists
|
||||
if ($(this).hasClass('selected')) {
|
||||
$(this).removeClass('selected');
|
||||
horizon.membership.remove_member_from_role(step_slug, data_id, new_role_id);
|
||||
} else {
|
||||
$(this).addClass('selected');
|
||||
horizon.membership.add_member_to_role(step_slug, data_id, new_role_id);
|
||||
}
|
||||
horizon.membership.update_member_role_dropdown(step_slug, data_id);
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Triggers on the addition of a new member via the inline object creation field.
|
||||
**/
|
||||
add_new_member: function(step_slug) {
|
||||
$("select[id='id_new_" + step_slug + "']").on('change', function (evt) {
|
||||
// add the member to the visible list
|
||||
var display_name = $(this).find("option").text();
|
||||
var data_id = $(this).find("option").attr("value");
|
||||
var default_role_id = horizon.membership.default_role_id[step_slug];
|
||||
$("." + step_slug + "_members").append(horizon.membership.generate_member_element(step_slug, display_name, data_id, [default_role_id], "-"));
|
||||
|
||||
// add the member to the hidden role lists and the data list
|
||||
horizon.membership.data[step_slug][data_id] = display_name;
|
||||
$("select[multiple='multiple']").append("<option value='" + data_id + "'>" + horizon.membership.data[step_slug][data_id] + "</option>");
|
||||
horizon.membership.add_member_to_role(step_slug, data_id, default_role_id);
|
||||
|
||||
// remove option from hidden select
|
||||
$(this).text("");
|
||||
|
||||
// reset lists and input filters
|
||||
horizon.membership.list_filtering(step_slug);
|
||||
horizon.membership.detect_no_results(step_slug);
|
||||
$("input.filter").val("");
|
||||
|
||||
// fix styling
|
||||
$("." + step_slug + "_members .btn-group").removeClass('last_stripe');
|
||||
$("." + step_slug + "_members .btn-group:last").addClass('last_stripe');
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Style the inline object creation button, hide the associated field.
|
||||
**/
|
||||
add_new_member_styling: function(step_slug) {
|
||||
var add_member_el = $("label[for='id_new_" + step_slug + "']").parent();
|
||||
$(add_member_el).find("select").hide();
|
||||
$("#add_" + step_slug).append($(add_member_el));
|
||||
$(add_member_el).addClass("add_" + step_slug);
|
||||
$(add_member_el).find("label, .input").addClass("add_" + step_slug + "_btn");
|
||||
},
|
||||
|
||||
/*
|
||||
* Fixes the striping of the fake table upon modification of the lists.
|
||||
**/
|
||||
fix_stripes: function(step_slug) {
|
||||
$('.fake_' + step_slug + '_table').each( function () {
|
||||
var filter = "." + $(this).attr('id');
|
||||
var visible = " .btn-group:visible";
|
||||
var even = " .btn-group:visible:even";
|
||||
var last = " .btn-group:visible:last";
|
||||
|
||||
// fix striping of rows
|
||||
$(filter + visible).removeClass('dark_stripe');
|
||||
$(filter + visible).addClass('light_stripe');
|
||||
$(filter + even).removeClass('light_stripe');
|
||||
$(filter + even).addClass('dark_stripe');
|
||||
|
||||
// fix bottom border of new last element
|
||||
$(filter + visible).removeClass('last_stripe');
|
||||
$(filter + last).addClass('last_stripe');
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Sets up filtering for each list of data.
|
||||
**/
|
||||
list_filtering: function (step_slug) {
|
||||
// remove previous lists' quicksearch events
|
||||
$('input.' + step_slug + '_filter').unbind();
|
||||
|
||||
// set up quicksearch to filter on input
|
||||
$('.' + step_slug + '_filterable').each(function () {
|
||||
var css_class = $(this).children().children('ul').attr('class');
|
||||
// Example value: members step_slug_members
|
||||
// Pick the class name that contains the step_slug
|
||||
var filter = _.find(css_class.split(' '), function(val){ return val.indexOf(step_slug) != -1; });
|
||||
|
||||
var input = $("input[id='" + filter +"']");
|
||||
input.quicksearch('ul.' + filter + ' ul li span.display_name', {
|
||||
'delay': 200,
|
||||
'loader': 'span.loading',
|
||||
'show': function () {
|
||||
$(this).parent().parent().show();
|
||||
if (filter == "available_" + step_slug) {
|
||||
$(this).parent('.dropdown-toggle').hide();
|
||||
}
|
||||
},
|
||||
'hide': function () {
|
||||
$(this).parent().parent().hide();
|
||||
},
|
||||
'noResults': 'ul#no_' + filter,
|
||||
'onAfter': function () {
|
||||
horizon.membership.fix_stripes(step_slug);
|
||||
},
|
||||
'prepareQuery': function (val) {
|
||||
return new RegExp(val, "i");
|
||||
},
|
||||
'testQuery': function (query, txt, span) {
|
||||
if ($(input).attr('id') == filter) {
|
||||
$(input).prev().removeAttr('disabled');
|
||||
return query.test($(span).text());
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Calls set-up functions upon loading the workflow.
|
||||
**/
|
||||
workflow_init: function(modal, step_slug, step_id) {
|
||||
// fix the dropdown menu overflow issues
|
||||
$(".tab-content, .workflow").addClass("dropdown_fix");
|
||||
|
||||
$(modal).find('form').each( function () {
|
||||
var $form = $(this);
|
||||
|
||||
// Do nothing if this isn't a membership modal
|
||||
if ($form.find('div.' + step_slug + '_membership').length == 0) {
|
||||
return; // continue
|
||||
}
|
||||
|
||||
// call the initalization functions
|
||||
horizon.membership.init_properties(step_slug);
|
||||
horizon.membership.generate_html(step_slug);
|
||||
horizon.membership.update_membership(step_slug);
|
||||
horizon.membership.select_member_role(step_slug);
|
||||
horizon.membership.add_new_member(step_slug);
|
||||
|
||||
|
||||
// initially hide role dropdowns for available member list
|
||||
$form.find(".available_" + step_slug + " .role_options").hide();
|
||||
|
||||
// hide the dropdown for members too if we don't need to show it
|
||||
if (!horizon.membership.has_roles[step_slug]) {
|
||||
$form.find("." + step_slug + "_members .role_options").hide();
|
||||
}
|
||||
|
||||
// unfocus filter fields
|
||||
if (step_id.indexOf('update') ==0) {
|
||||
$form.find("#" + step_id + " input").blur();
|
||||
}
|
||||
|
||||
// prevent filter inputs from submitting form on 'enter'
|
||||
$form.find('.' + step_slug + '_membership').keydown(function(event){
|
||||
if(event.keyCode == 13) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// add filtering + styling to the inline obj creation btn
|
||||
horizon.membership.add_new_member_styling(step_slug);
|
||||
horizon.membership.list_filtering(step_slug);
|
||||
horizon.membership.detect_no_results(step_slug);
|
||||
|
||||
// fix initial striping of rows
|
||||
$form.find('.fake_' + step_slug + '_table').each( function () {
|
||||
var filter = "." + $(this).attr('id');
|
||||
$(filter + ' .btn-group:even').addClass('dark_stripe');
|
||||
$(filter + ' .btn-group:last').addClass('last_stripe');
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
@ -1,474 +0,0 @@
|
||||
/* Namespace for core functionality related to Project Workflows. */
|
||||
horizon.projects = {
|
||||
|
||||
current_membership: [],
|
||||
users: [],
|
||||
roles: [],
|
||||
has_roles: true,
|
||||
default_role_id: "",
|
||||
|
||||
/* Parses the form field selector's ID to get either the
|
||||
* role or user id (i.e. returns "id12345" when
|
||||
* passed the selector with id: "id_user_id12345").
|
||||
**/
|
||||
get_field_id: function(id_string) {
|
||||
return id_string.slice(id_string.lastIndexOf("_") + 1);
|
||||
},
|
||||
|
||||
/*
|
||||
* Gets the html select element associated with a given
|
||||
* role id.
|
||||
**/
|
||||
get_role_element: function(role_id) {
|
||||
return $('select[id^="id_role_' + role_id + '"]');
|
||||
},
|
||||
|
||||
/*
|
||||
* Gets the html ul element associated with a given
|
||||
* user id. I.e., the user's row.
|
||||
**/
|
||||
get_user_element: function(user_id) {
|
||||
return $('li[data-user-id$=' + user_id + ']').parent();
|
||||
},
|
||||
|
||||
/*
|
||||
* Initializes all of the horizon.projects lists with
|
||||
* data parsed from the hidden form fields, as well as the
|
||||
* default role id.
|
||||
**/
|
||||
init_properties: function() {
|
||||
horizon.projects.has_roles = $(".project_membership").data('show-roles') !== "no";
|
||||
horizon.projects.default_role_id = $('#id_default_role').attr('value');
|
||||
horizon.projects.init_user_list();
|
||||
horizon.projects.init_role_list();
|
||||
horizon.projects.init_current_membership();
|
||||
},
|
||||
|
||||
/*
|
||||
* Initializes an associative array mapping user ids to user names.
|
||||
**/
|
||||
init_user_list: function() {
|
||||
horizon.projects.users = [];
|
||||
_.each($(this.get_role_element("")).find("option"), function (option) {
|
||||
horizon.projects.users[option.value] = option.text;
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Initializes an associative array mapping role ids to role names.
|
||||
**/
|
||||
init_role_list: function() {
|
||||
horizon.projects.roles = [];
|
||||
_.each($('label[for^="id_role_"]'), function(role) {
|
||||
var id = horizon.projects.get_field_id($(role).attr('for'));
|
||||
horizon.projects.roles[id] = $(role).text();
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Initializes an associative array of lists of the current
|
||||
* members for each available role.
|
||||
**/
|
||||
init_current_membership: function() {
|
||||
horizon.projects.current_membership = [];
|
||||
var members_list = [];
|
||||
var role_name, role_id, selected_members;
|
||||
_.each(this.get_role_element(''), function(value, key) {
|
||||
role_id = horizon.projects.get_field_id($(value).attr('id'));
|
||||
role_name = $('label[for="id_role_' + role_id + '"]').text();
|
||||
|
||||
// get the array of members who are selected in this list
|
||||
selected_members = $(value).find("option:selected");
|
||||
// extract the member names and add them to the dictionary of lists
|
||||
members_list = [];
|
||||
if (selected_members) {
|
||||
_.each(selected_members, function(member) {
|
||||
members_list.push(member.value);
|
||||
});
|
||||
}
|
||||
horizon.projects.current_membership[role_id] = members_list;
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns the ids of roles the user is a member of.
|
||||
**/
|
||||
get_user_roles: function(user_id) {
|
||||
var roles = [];
|
||||
for (var role in horizon.projects.current_membership) {
|
||||
if ($.inArray(user_id, horizon.projects.current_membership[role]) >= 0) {
|
||||
roles.push(role);
|
||||
}
|
||||
}
|
||||
return roles;
|
||||
},
|
||||
|
||||
/*
|
||||
* Updates the selected values on the role_list's form field, as
|
||||
* well as the current_membership dictionary's list.
|
||||
**/
|
||||
update_role_lists: function(role_id, new_list) {
|
||||
this.get_role_element(role_id).val(new_list);
|
||||
horizon.projects.current_membership[role_id] = new_list;
|
||||
},
|
||||
|
||||
/*
|
||||
* Helper function for remove_user_from_role.
|
||||
**/
|
||||
remove_user: function(user_id, role_id, role_list) {
|
||||
var index = role_list.indexOf(user_id);
|
||||
if (index >= 0) {
|
||||
// remove member from list
|
||||
role_list.splice(index, 1);
|
||||
horizon.projects.update_role_lists(role_id, role_list);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Searches through the role lists and removes a given user
|
||||
* from the lists.
|
||||
**/
|
||||
remove_user_from_role: function(user_id, role_id) {
|
||||
var role_list;
|
||||
if (role_id) {
|
||||
role_list = horizon.projects.current_membership[role_id];
|
||||
horizon.projects.remove_user(user_id, role_id, role_list);
|
||||
}
|
||||
else {
|
||||
// search for membership in role lists
|
||||
for (var role in horizon.projects.current_membership) {
|
||||
role_list = horizon.projects.current_membership[role];
|
||||
horizon.projects.remove_user(user_id, role, role_list);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Adds a given user to a given role list.
|
||||
**/
|
||||
add_user_to_role: function(user_id, role_id) {
|
||||
var role_list = horizon.projects.current_membership[role_id];
|
||||
role_list.push(user_id);
|
||||
horizon.projects.update_role_lists(role_id, role_list);
|
||||
},
|
||||
|
||||
update_user_role_dropdown: function(user_id, role_ids, user_el) {
|
||||
if (typeof(role_ids) === 'undefined') {
|
||||
role_ids = horizon.projects.get_user_roles(user_id);
|
||||
}
|
||||
if (typeof(user_el) === 'undefined') {
|
||||
user_el = horizon.projects.get_user_element(user_id);
|
||||
}
|
||||
|
||||
var $dropdown = user_el.find("li.member").siblings('.dropdown');
|
||||
var $role_items = $dropdown.children('.role_dropdown').children('li');
|
||||
|
||||
$role_items.each(function (idx, el) {
|
||||
if (_.contains(role_ids, $(el).data('role-id'))) {
|
||||
$(el).addClass('selected');
|
||||
} else {
|
||||
$(el).removeClass('selected');
|
||||
}
|
||||
});
|
||||
|
||||
// set the selection back to default role
|
||||
var $roles_display = $dropdown.children('.dropdown-toggle').children('.roles_display');
|
||||
var roles_to_display = [];
|
||||
for (var i = 0; i < role_ids.length; i++) {
|
||||
if (i == 2) {
|
||||
roles_to_display.push('...');
|
||||
break;
|
||||
}
|
||||
roles_to_display.push(horizon.projects.roles[role_ids[i]]);
|
||||
}
|
||||
text = roles_to_display.join(', ');
|
||||
if (text.length == 0) text = 'No roles';
|
||||
$roles_display.text(text);
|
||||
},
|
||||
|
||||
/*
|
||||
* Generates the HTML structure for a user that will be displayed
|
||||
* as a list item in the project member list.
|
||||
**/
|
||||
generate_user_element: function(user_name, user_id, role_ids, text) {
|
||||
var str_id = "id_user_" + user_id;
|
||||
|
||||
var roles = [];
|
||||
for (var r in horizon.projects.roles) {
|
||||
var role = {};
|
||||
role['role_id'] = r;
|
||||
role['role_name'] = horizon.projects.roles[r];
|
||||
roles.push(role);
|
||||
}
|
||||
|
||||
var template = horizon.templates.compiled_templates["#project_user_template"],
|
||||
params = {user_id: str_id,
|
||||
default_role: horizon.projects.roles[horizon.projects.default_role_id],
|
||||
user_name: user_name,
|
||||
text: text,
|
||||
roles: roles},
|
||||
user_el = $(template.render(params));
|
||||
this.update_user_role_dropdown(str_id, role_ids, user_el);
|
||||
return $(user_el);
|
||||
},
|
||||
|
||||
/*
|
||||
* Generates the HTML structure for the project membership UI.
|
||||
**/
|
||||
generate_html: function() {
|
||||
var user;
|
||||
for (user in horizon.projects.users) {
|
||||
var user_id = user;
|
||||
var user_name = horizon.projects.users[user];
|
||||
var role_ids = this.get_user_roles(user_id);
|
||||
if (role_ids.length > 0) {
|
||||
$(".project_members").append(this.generate_user_element(user_name, user_id, role_ids, "-"));
|
||||
}
|
||||
else {
|
||||
$(".available_users").append(this.generate_user_element(user_name, user_id, role_ids, "+"));
|
||||
}
|
||||
}
|
||||
horizon.projects.detect_no_results();
|
||||
},
|
||||
|
||||
/*
|
||||
* Triggers on click of link to add/remove member from the project.
|
||||
**/
|
||||
update_membership: function() {
|
||||
$(".available_users, .project_members").on('click', ".btn-group a[href='#add_remove']", function (evt) {
|
||||
evt.preventDefault();
|
||||
var available = $(".available_users").has($(this)).length;
|
||||
var user_id = horizon.projects.get_field_id($(this).parent().siblings().attr('data-user-id'));
|
||||
var user_el = $(this).parent().parent();
|
||||
|
||||
if (available) {
|
||||
var default_role = horizon.projects.default_role_id;
|
||||
$(this).text("-");
|
||||
$(".project_members").append(user_el);
|
||||
horizon.projects.add_user_to_role(user_id, default_role);
|
||||
|
||||
if (horizon.projects.has_roles) {
|
||||
$(this).parent().siblings(".role_options").show();
|
||||
horizon.projects.update_user_role_dropdown(user_id, [default_role], user_el);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$(this).text("+");
|
||||
$(this).parent().siblings(".role_options").hide();
|
||||
$(".available_users").append(user_el);
|
||||
horizon.projects.remove_user_from_role(user_id);
|
||||
}
|
||||
|
||||
// update lists
|
||||
horizon.projects.list_filtering();
|
||||
horizon.projects.detect_no_results();
|
||||
|
||||
// remove input filters
|
||||
$("input.filter").val("");
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Detects whether each list has members and if it does not
|
||||
* displays a message to the user.
|
||||
**/
|
||||
detect_no_results: function () {
|
||||
$('.filterable').each( function () {
|
||||
var filter = $(this).find('ul').attr('class');
|
||||
|
||||
if (!$('.' + filter).children('ul').length) {
|
||||
$('#no_' + filter).show();
|
||||
$("input[id='" + filter + "']").attr('disabled', 'disabled');
|
||||
}
|
||||
else {
|
||||
$('#no_' + filter).hide();
|
||||
$("input[id='" + filter + "']").removeAttr('disabled');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Triggers on selection of new role for a member.
|
||||
**/
|
||||
select_member_role: function() {
|
||||
$(".available_users, .project_members").on('click', '.role_dropdown li', function (evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
// get the newly selected role and the member's name
|
||||
var new_role_id = $(this).attr("data-role-id");
|
||||
var id_str = $(this).parent().parent().siblings(".member").attr("data-user-id");
|
||||
var user_id = horizon.projects.get_field_id(id_str);
|
||||
|
||||
// update role lists
|
||||
if ($(this).hasClass('selected')) {
|
||||
$(this).removeClass('selected');
|
||||
horizon.projects.remove_user_from_role(user_id, new_role_id);
|
||||
} else {
|
||||
$(this).addClass('selected');
|
||||
horizon.projects.add_user_to_role(user_id, new_role_id);
|
||||
}
|
||||
horizon.projects.update_user_role_dropdown(user_id);
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Triggers on the addition of a new user via the inline object creation field.
|
||||
**/
|
||||
add_new_user: function() {
|
||||
$("select[id='id_new_user']").on('change', function (evt) {
|
||||
// add the user to the visible list
|
||||
var user_name = $(this).find("option").text();
|
||||
var user_id = $(this).find("option").attr("value");
|
||||
var default_role_id = horizon.projects.default_role_id;
|
||||
$(".project_members").append(horizon.projects.generate_user_element(user_name, user_id, [default_role_id], "-"));
|
||||
|
||||
// add the user to the hidden role lists and the users list
|
||||
horizon.projects.users[user_id] = user_name;
|
||||
$("select[multiple='multiple']").append("<option value='" + user_id + "'>" + horizon.projects.users[user_id] + "</option>");
|
||||
horizon.projects.add_user_to_role(user_id, default_role_id);
|
||||
|
||||
// remove option from hidden select
|
||||
$(this).text("");
|
||||
|
||||
// reset lists and input filters
|
||||
horizon.projects.list_filtering();
|
||||
horizon.projects.detect_no_results();
|
||||
$("input.filter").val("");
|
||||
|
||||
// fix styling
|
||||
$(".project_members .btn-group").removeClass('last_stripe');
|
||||
$(".project_members .btn-group:last").addClass('last_stripe');
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Style the inline object creation button, hide the associated field.
|
||||
**/
|
||||
add_new_user_styling: function() {
|
||||
var add_user_el = $("label[for='id_new_user']").parent();
|
||||
$(add_user_el).find("select").hide();
|
||||
$("#add_user").append($(add_user_el));
|
||||
$(add_user_el).addClass("add_user");
|
||||
$(add_user_el).find("label, .input").addClass("add_user_btn");
|
||||
},
|
||||
|
||||
/*
|
||||
* Fixes the striping of the fake table upon modification of the lists.
|
||||
**/
|
||||
fix_stripes: function() {
|
||||
$('.fake_table').each( function () {
|
||||
var filter = "." + $(this).attr('id');
|
||||
var visible = " .btn-group:visible";
|
||||
var even = " .btn-group:visible:even";
|
||||
var last = " .btn-group:visible:last";
|
||||
|
||||
// fix striping of rows
|
||||
$(filter + visible).removeClass('dark_stripe');
|
||||
$(filter + visible).addClass('light_stripe');
|
||||
$(filter + even).removeClass('light_stripe');
|
||||
$(filter + even).addClass('dark_stripe');
|
||||
|
||||
// fix bottom border of new last element
|
||||
$(filter + visible).removeClass('last_stripe');
|
||||
$(filter + last).addClass('last_stripe');
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Sets up filtering for each list of users.
|
||||
**/
|
||||
list_filtering: function () {
|
||||
// remove previous lists' quicksearch events
|
||||
$('input.filter').unbind();
|
||||
|
||||
// set up quicksearch to filter on input
|
||||
$('.filterable').each(function () {
|
||||
var filter = $(this).children().children('ul').attr('class');
|
||||
var input = $("input[id='" + filter +"']");
|
||||
input.quicksearch('ul.' + filter + ' ul li span.user_name', {
|
||||
'delay': 200,
|
||||
'loader': 'span.loading',
|
||||
'show': function () {
|
||||
$(this).parent().parent().show();
|
||||
if (filter == "available_users") {
|
||||
$(this).parent('.dropdown-toggle').hide();
|
||||
}
|
||||
},
|
||||
'hide': function () {
|
||||
$(this).parent().parent().hide();
|
||||
},
|
||||
'noResults': 'ul#no_' + filter,
|
||||
'onAfter': function () {
|
||||
horizon.projects.fix_stripes();
|
||||
},
|
||||
'prepareQuery': function (val) {
|
||||
return new RegExp(val, "i");
|
||||
},
|
||||
'testQuery': function (query, txt, span) {
|
||||
if ($(input).attr('id') == filter) {
|
||||
$(input).prev().removeAttr('disabled');
|
||||
return query.test($(span).text());
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Calls set-up functions upon loading the workflow.
|
||||
**/
|
||||
workflow_init: function(modal) {
|
||||
// fix the dropdown menu overflow issues
|
||||
$(".tab-content, .workflow").addClass("dropdown_fix");
|
||||
|
||||
$(modal).find('form').each( function () {
|
||||
var $form = $(this);
|
||||
|
||||
// Do nothing if this isn't a membership modal
|
||||
if ($form.find('div.project_membership').length == 0) {
|
||||
return; // continue
|
||||
}
|
||||
|
||||
// call the initalization functions
|
||||
horizon.projects.init_properties();
|
||||
horizon.projects.generate_html();
|
||||
horizon.projects.update_membership();
|
||||
horizon.projects.select_member_role();
|
||||
horizon.projects.add_new_user();
|
||||
|
||||
// initially hide role dropdowns for available users list
|
||||
$form.find(".available_users .role_options").hide();
|
||||
|
||||
// hide the dropdown for members too if we don't need to show it
|
||||
if (!horizon.projects.has_roles) {
|
||||
$form.find(".project_members .role_options").hide();
|
||||
}
|
||||
|
||||
// unfocus filter fields
|
||||
$form.find("#update_project__update_members input").blur();
|
||||
|
||||
// prevent filter inputs from submitting form on 'enter'
|
||||
$form.find('.project_membership').keydown(function(event){
|
||||
if(event.keyCode == 13) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// add filtering + styling to the inline obj creation btn
|
||||
horizon.projects.add_new_user_styling();
|
||||
horizon.projects.list_filtering();
|
||||
horizon.projects.detect_no_results();
|
||||
|
||||
// fix initial striping of rows
|
||||
$form.find('.fake_table').each( function () {
|
||||
var filter = "." + $(this).attr('id');
|
||||
$(filter + ' .btn-group:even').addClass('dark_stripe');
|
||||
$(filter + ' .btn-group:last').addClass('last_stripe');
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
/* Namespace for core functionality related to client-side templating. */
|
||||
horizon.templates = {
|
||||
template_ids: ["#modal_template", "#empty_row_template", "#alert_message_template", "#spinner-modal", "#project_user_template"],
|
||||
template_ids: ["#modal_template", "#empty_row_template", "#alert_message_template", "#spinner-modal", "#membership_template"],
|
||||
compiled_templates: {}
|
||||
};
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.templates.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.users.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.utils.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.projects.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.membership.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.networktopology.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.d3piechart.js' type='text/javascript' charset='utf-8'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/horizon.heattop.js' type='text/javascript' charset='utf-8'></script>
|
||||
|
@ -1,13 +1,13 @@
|
||||
{% extends "horizon/client_side/template.html" %}
|
||||
{% load horizon %}
|
||||
|
||||
{% block id %}project_user_template{% endblock %}
|
||||
{% block id %}membership_template{% endblock %}
|
||||
|
||||
{% block template %}
|
||||
{% jstemplate %}
|
||||
<ul class="nav nav-pills btn-group">
|
||||
<li class="member" data-user-id="[[user_id]]">
|
||||
<span class="user_name">[[user_name]]</span>
|
||||
<li class="member" data-[[step_slug]]-id="[[data_id]]">
|
||||
<span class="display_name">[[display_name]]</span>
|
||||
</li>
|
||||
<li class="active"><a class="btn btn-primary" href="#add_remove">[[text]]</a></li>
|
||||
<li class="dropdown role_options">
|
@ -2,4 +2,4 @@
|
||||
{% include "horizon/client_side/_table_row.html" %}
|
||||
{% include "horizon/client_side/_alert_message.html" %}
|
||||
{% include "horizon/client_side/_loading.html" %}
|
||||
{% include "horizon/client_side/_project_user.html" %}
|
||||
{% include "horizon/client_side/_membership.html" %}
|
@ -2,34 +2,34 @@
|
||||
|
||||
<noscript><h3>{{ step }}</h3></noscript>
|
||||
|
||||
<div class="project_membership" data-show-roles="{{ step.show_roles|yesno }}">
|
||||
<div class="membership {{ step.slug }}_membership" data-show-roles="{{ step.show_roles|yesno }}">
|
||||
<div class="header">
|
||||
<div class="help_text">{{ step.help_text }}</div>
|
||||
<div class="left">
|
||||
<div class="fake_table fake_table_header">
|
||||
<span class="users_title">{{ step.available_list_title }}</span>
|
||||
<input type="text" name="available_users_filter" id="available_users" class="filter" placeholder="Filter">
|
||||
<div class="fake_table fake_table_header fake_{{ step.slug }}_table">
|
||||
<span class="members_title">{{ step.available_list_title }}</span>
|
||||
<input type="text" name="available_{{ step.slug }}_filter" id="available_{{ step.slug }}" class="filter {{ step.slug }}_filter" placeholder="Filter">
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="fake_table fake_table_header">
|
||||
<span class="users_title">{{ step.members_list_title }}</span>
|
||||
<input type="text" name="project_members_filter" id="project_members" class="filter" placeholder="Filter">
|
||||
<div class="fake_table fake_table_header fake_{{ step.slug }}_table">
|
||||
<span class="members_title">{{ step.members_list_title }}</span>
|
||||
<input type="text" name="{{ step.slug }}_members_filter" id="{{ step.slug }}_members" class="filter {{ step.slug }}_filter" placeholder="Filter">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="left filterable">
|
||||
<div class="fake_table" id="available_users">
|
||||
<ul class="available_users"></ul>
|
||||
<ul class="no_results" id="no_available_users"><li>{{ step.no_available_text }}</li></ul>
|
||||
<div class="left filterable {{ step.slug }}_filterable">
|
||||
<div class="fake_table fake_{{ step.slug }}_table" id="available_{{ step.slug }}">
|
||||
<ul class="available_members available_{{ step.slug }}"></ul>
|
||||
<ul class="no_results" id="no_available_{{ step.slug }}"><li>{{ step.no_available_text }}</li></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right filterable">
|
||||
<div class="fake_table" id="project_members">
|
||||
<ul class="project_members"></ul>
|
||||
<ul class="no_results" id="no_project_members"><li>{{ step.no_members_text }}</li></ul>
|
||||
<div class="right filterable {{ step.slug }}_filterable">
|
||||
<div class="fake_table fake_{{ step.slug }}_table" id="{{ step.slug }}_members">
|
||||
<ul class="members {{ step.slug }}_members"></ul>
|
||||
<ul class="no_results" id="no_{{ step.slug }}_members"><li>{{ step.no_members_text }}</li></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -41,10 +41,10 @@
|
||||
|
||||
<script>
|
||||
if (typeof $ !== 'undefined') {
|
||||
horizon.projects.workflow_init($(".workflow"));
|
||||
horizon.membership.workflow_init($(".workflow"), "{{ step.slug }}", "{{ step.get_id }}");
|
||||
} else {
|
||||
addHorizonLoadEvent(function() {
|
||||
horizon.projects.workflow_init($(".workflow"));
|
||||
horizon.membership.workflow_init($(".workflow"), "{{ step.slug }}", "{{ step.get_id }}");
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
@ -1,10 +1,12 @@
|
||||
from horizon.workflows.base import Action
|
||||
from horizon.workflows.base import MembershipAction
|
||||
from horizon.workflows.base import Step
|
||||
from horizon.workflows.base import UpdateMembersStep
|
||||
from horizon.workflows.base import Workflow
|
||||
from horizon.workflows.views import WorkflowView
|
||||
|
||||
assert Action
|
||||
assert MembershipAction
|
||||
assert Step
|
||||
assert UpdateMembersStep
|
||||
assert Workflow
|
||||
|
@ -144,7 +144,7 @@ class Action(forms.Form):
|
||||
return force_unicode(self.name)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s>" % (self.__class__.__name__, self.slug)
|
||||
return "<%s: %s>" % (self.__class__.__name_get_step_, self.slug)
|
||||
|
||||
def _populate_choices(self, request, context):
|
||||
for field_name, bound_field in self.fields.items():
|
||||
@ -181,6 +181,20 @@ class Action(forms.Form):
|
||||
return None
|
||||
|
||||
|
||||
class MembershipAction(Action):
|
||||
"""
|
||||
An action that allows a user to add/remove members from a group.
|
||||
|
||||
Extend the Action class with additional helper method for membership
|
||||
management.
|
||||
"""
|
||||
def get_default_role_field_name(self):
|
||||
return "default_" + self.slug + "_role"
|
||||
|
||||
def get_member_field_name(self, role_id):
|
||||
return self.slug + "_role_" + role_id
|
||||
|
||||
|
||||
class Step(object):
|
||||
"""
|
||||
A step is a wrapper around an action which defines it's context in a
|
||||
@ -470,6 +484,12 @@ class UpdateMembersStep(Step):
|
||||
no_available_text = _("None available.")
|
||||
no_members_text = _("No members.")
|
||||
|
||||
def get_member_field_name(self, role_id):
|
||||
if issubclass(self.action_class, MembershipAction):
|
||||
return self.action.get_member_field_name(role_id)
|
||||
else:
|
||||
return self.slug + "_role_" + role_id
|
||||
|
||||
|
||||
class Workflow(html.HTMLElement):
|
||||
"""
|
||||
|
@ -1,33 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from horizon import forms
|
||||
|
||||
from openstack_dashboard.dashboards.admin.users.forms import CreateUserForm
|
||||
|
||||
|
||||
class CreateUser(CreateUserForm):
|
||||
role_id = forms.ChoiceField(widget=forms.HiddenInput())
|
||||
project = forms.CharField(widget=forms.HiddenInput())
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(CreateUser, self).__init__(request, *args, **kwargs)
|
||||
project = self.request.path.split("/")[-1]
|
||||
self.fields['project'].initial = project
|
@ -4,13 +4,10 @@ from django.core.urlresolvers import reverse
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
from openstack_dashboard.dashboards.admin.users.tables import UsersTable
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -108,61 +105,3 @@ class TenantsTable(tables.DataTable):
|
||||
table_actions = (TenantFilterAction, CreateProject,
|
||||
DeleteTenantsAction)
|
||||
pagination_param = "tenant_marker"
|
||||
|
||||
|
||||
class RemoveUserAction(tables.BatchAction):
|
||||
name = "remove_user"
|
||||
action_present = _("Remove")
|
||||
action_past = _("Removed")
|
||||
data_type_singular = _("User")
|
||||
data_type_plural = _("Users")
|
||||
classes = ('btn-danger',)
|
||||
|
||||
def action(self, request, user_id):
|
||||
tenant_id = self.table.kwargs['tenant_id']
|
||||
api.keystone.remove_tenant_user(request, tenant_id, user_id)
|
||||
|
||||
|
||||
class ProjectUserRolesColumn(tables.Column):
|
||||
def get_raw_data(self, user):
|
||||
request = self.table.request
|
||||
try:
|
||||
roles = api.keystone.roles_for_user(request,
|
||||
user.id,
|
||||
self.table.kwargs["tenant_id"])
|
||||
except:
|
||||
roles = []
|
||||
exceptions.handle(request,
|
||||
_("Unable to retrieve role information."))
|
||||
return ", ".join([role.name for role in roles])
|
||||
|
||||
|
||||
class TenantUsersTable(UsersTable):
|
||||
roles = ProjectUserRolesColumn("roles", verbose_name=_("Roles"))
|
||||
|
||||
class Meta:
|
||||
name = "tenant_users"
|
||||
verbose_name = _("Users For Project")
|
||||
table_actions = (RemoveUserAction,)
|
||||
row_actions = (RemoveUserAction,)
|
||||
columns = ("name", "email", "id", "roles", "enabled")
|
||||
|
||||
|
||||
class AddUserAction(tables.LinkAction):
|
||||
name = "add_user"
|
||||
verbose_name = _("Add To Project")
|
||||
url = "horizon:admin:projects:add_user"
|
||||
classes = ('ajax-modal',)
|
||||
|
||||
def get_link_url(self, user):
|
||||
tenant_id = self.table.kwargs['tenant_id']
|
||||
return reverse(self.url, args=(tenant_id, user.id))
|
||||
|
||||
|
||||
class AddUsersTable(UsersTable):
|
||||
class Meta:
|
||||
name = "add_users"
|
||||
verbose_name = _("Add New Users")
|
||||
table_actions = ()
|
||||
row_actions = (AddUserAction,)
|
||||
columns = ("name", "email", "id", "enabled")
|
||||
|
@ -1,26 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}add_user_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:projects:add_user' tenant_id user_id %}{% endblock %}
|
||||
|
||||
{% block modal_id %}add_user_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Add User To Project" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
<p>{% trans "Select the user role for the project." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Add" %}" />
|
||||
<a href="{% url 'horizon:admin:projects:users' tenant_id %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -1,26 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}create_tenant_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:projects:create' %}{% endblock %}
|
||||
|
||||
{% block modal_id %}create_tenant_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Create Project" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
<p>{% trans "From here you can create a new project to organize users." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Project" %}" />
|
||||
<a href="{% url 'horizon:admin:projects:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -1,26 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}create_user_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:projects:create_user' tenant_id %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% blocktrans %}Create User for project '{{ tenant_name }}'.{% endblocktrans %}{% endblock %}
|
||||
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
<p>{% trans "From here you can create a new user to add to this project." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create User" %}" />
|
||||
<a href="{% url 'horizon:admin:projects:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -1,25 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}quota_update_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:projects:quotas' tenant.id %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Update Quota" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
<p>{% blocktrans with tenant_id=tenant.id %}From here you can edit quotas (max limits) for the project {{ tenant.name }}.{% endblocktrans %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Update Quota" %}" />
|
||||
<a href="{% url 'horizon:admin:projects:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -1,26 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:projects:update' tenant.id %}{% endblock %}
|
||||
|
||||
{% block modal_id %}update_tenant_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Update Project" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
<p>{% trans "From here you can edit a project." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Update Project" %}" />
|
||||
<a href="{% url 'horizon:admin:projects:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -1,39 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<noscript><h3>{{ step }}</h3></noscript>
|
||||
|
||||
<div class="project_membership">
|
||||
<div class="header">
|
||||
<div class="help_text">{% trans "From here you can add and remove members to this project from the list of all available users." %}</div>
|
||||
<div class="left">
|
||||
<div class="fake_table fake_table_header">
|
||||
<span class="users_title">{% trans "All Users" %}</span>
|
||||
<input type="text" name="available_users_filter" id="available_users" class="filter" value="Filter">
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="fake_table fake_table_header">
|
||||
<span class="users_title">{% trans "Project Members" %}</span>
|
||||
<input type="text" name="project_members_filter" id="project_members" class="filter" value="Filter">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="left filterable">
|
||||
<div class="fake_table" id="available_users">
|
||||
<ul class="available_users"></ul>
|
||||
<ul class="no_results" id="no_available_users"><li>{% trans "No users found." %}</li></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right filterable">
|
||||
<div class="fake_table" id="project_members">
|
||||
<ul class="project_members"></ul>
|
||||
<ul class="no_results" id="no_project_members"><li>{% trans "No users found." %}</li></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hide">
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</div>
|
@ -1,11 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Add User To Project" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Add User To Project") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'admin/projects/_add_user.html' %}
|
||||
{% endblock %}
|
@ -1,12 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Add New User" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Add New User") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_create_user.html' %}
|
||||
{% endblock %}
|
@ -1,11 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Modify Project Quotas" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Update Project") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'admin/projects/_quotas.html' with form=form %}
|
||||
{% endblock %}
|
@ -1,18 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Project Users" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
<h2>{% trans "Users for Project" %}: <span>{{ tenant.name }}</span></h2>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div id="tenant_users_table">
|
||||
{{ tenant_users_table.render }}
|
||||
</div>
|
||||
<div id="add_users_table">
|
||||
{{ add_users_table.render }}
|
||||
</div>
|
||||
{% endblock %}
|
@ -30,10 +30,14 @@ from openstack_dashboard.usage import quotas
|
||||
|
||||
from openstack_dashboard.dashboards.admin.projects.workflows \
|
||||
import CreateProject
|
||||
from openstack_dashboard.dashboards.admin.projects.workflows \
|
||||
import PROJECT_USER_MEMBER_SLUG
|
||||
from openstack_dashboard.dashboards.admin.projects.workflows \
|
||||
import UpdateProject
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:admin:projects:index')
|
||||
USER_ROLE_PREFIX = PROJECT_USER_MEMBER_SLUG + "_role_"
|
||||
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',)})
|
||||
@ -176,10 +180,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
.AndReturn(default_role)
|
||||
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
|
||||
.AndReturn(users)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
|
||||
# contribute
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(roles)
|
||||
|
||||
# handle
|
||||
project_details = self._get_project_info(project)
|
||||
@ -188,12 +190,10 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
api.keystone.tenant_create(IsA(http.HttpRequest), **project_details) \
|
||||
.AndReturn(project)
|
||||
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
|
||||
workflow_data = {}
|
||||
for role in roles:
|
||||
if "role_" + role.id in workflow_data:
|
||||
ulist = workflow_data["role_" + role.id]
|
||||
if USER_ROLE_PREFIX + role.id in workflow_data:
|
||||
ulist = workflow_data[USER_ROLE_PREFIX + role.id]
|
||||
for user_id in ulist:
|
||||
api.keystone.add_tenant_user_role(IsA(http.HttpRequest),
|
||||
project=self.tenant.id,
|
||||
@ -245,7 +245,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
.AndReturn(default_role)
|
||||
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
|
||||
.AndReturn(users)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(roles)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
@ -281,10 +282,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
.AndReturn(default_role)
|
||||
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
|
||||
.AndReturn(users)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
|
||||
# contribute
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(roles)
|
||||
|
||||
# handle
|
||||
project_details = self._get_project_info(project)
|
||||
@ -330,10 +329,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
.AndReturn(default_role)
|
||||
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
|
||||
.AndReturn(users)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
|
||||
# contribute
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(roles)
|
||||
|
||||
# handle
|
||||
project_details = self._get_project_info(project)
|
||||
@ -342,12 +339,10 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
api.keystone.tenant_create(IsA(http.HttpRequest), **project_details) \
|
||||
.AndReturn(project)
|
||||
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
|
||||
workflow_data = {}
|
||||
for role in roles:
|
||||
if "role_" + role.id in workflow_data:
|
||||
ulist = workflow_data["role_" + role.id]
|
||||
if USER_ROLE_PREFIX + role.id in workflow_data:
|
||||
ulist = workflow_data[USER_ROLE_PREFIX + role.id]
|
||||
for user_id in ulist:
|
||||
api.keystone.add_tenant_user_role(IsA(http.HttpRequest),
|
||||
project=self.tenant.id,
|
||||
@ -400,10 +395,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
.AndReturn(default_role)
|
||||
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
|
||||
.AndReturn(users)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
|
||||
# contribute
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(roles)
|
||||
|
||||
# handle
|
||||
project_details = self._get_project_info(project)
|
||||
@ -412,12 +405,10 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
api.keystone.tenant_create(IsA(http.HttpRequest), **project_details) \
|
||||
.AndReturn(project)
|
||||
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
|
||||
workflow_data = {}
|
||||
for role in roles:
|
||||
if "role_" + role.id in workflow_data:
|
||||
ulist = workflow_data["role_" + role.id]
|
||||
if USER_ROLE_PREFIX + role.id in workflow_data:
|
||||
ulist = workflow_data[USER_ROLE_PREFIX + role.id]
|
||||
for user_id in ulist:
|
||||
api.keystone.add_tenant_user_role(IsA(http.HttpRequest),
|
||||
project=self.tenant.id,
|
||||
@ -474,10 +465,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
.AndReturn(default_role)
|
||||
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
|
||||
.AndReturn(users)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
|
||||
# contribute
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(roles)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
@ -546,7 +535,8 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
.AndReturn(default_role)
|
||||
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
|
||||
.AndReturn(users)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(roles)
|
||||
|
||||
for user in users:
|
||||
api.keystone.roles_for_user(IsA(http.HttpRequest),
|
||||
@ -614,7 +604,8 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
.AndReturn(default_role)
|
||||
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
|
||||
.AndReturn(users)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(roles)
|
||||
|
||||
workflow_data = {}
|
||||
for user in users:
|
||||
@ -622,8 +613,8 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
user.id,
|
||||
self.tenant.id).AndReturn(roles)
|
||||
|
||||
workflow_data["role_1"] = ['3'] # admin role
|
||||
workflow_data["role_2"] = ['2'] # member role
|
||||
workflow_data[USER_ROLE_PREFIX + "1"] = ['3'] # admin role
|
||||
workflow_data[USER_ROLE_PREFIX + "2"] = ['2'] # member role
|
||||
|
||||
# update some fields
|
||||
project._info["name"] = "updated name"
|
||||
@ -636,16 +627,12 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
"enabled": project.enabled}
|
||||
updated_quota = self._get_quota_info(quota)
|
||||
|
||||
# contribute
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
|
||||
# handle
|
||||
api.keystone.tenant_update(IsA(http.HttpRequest),
|
||||
project.id,
|
||||
**updated_project) \
|
||||
.AndReturn(project)
|
||||
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.user_list(IsA(http.HttpRequest),
|
||||
project=self.tenant.id).AndReturn(proj_users)
|
||||
|
||||
@ -763,7 +750,8 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
.AndReturn(default_role)
|
||||
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
|
||||
.AndReturn(users)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(roles)
|
||||
|
||||
workflow_data = {}
|
||||
for user in users:
|
||||
@ -772,7 +760,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
self.tenant.id).AndReturn(roles)
|
||||
role_ids = [role.id for role in roles]
|
||||
if role_ids:
|
||||
workflow_data.setdefault("role_" + role_ids[0], []) \
|
||||
workflow_data.setdefault(USER_ROLE_PREFIX + role_ids[0], []) \
|
||||
.append(user.id)
|
||||
|
||||
# update some fields
|
||||
@ -786,9 +774,6 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
"enabled": project.enabled}
|
||||
updated_quota = self._get_quota_info(quota)
|
||||
|
||||
# contribute
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
|
||||
# handle
|
||||
api.keystone.tenant_update(IsA(http.HttpRequest),
|
||||
project.id,
|
||||
@ -848,7 +833,8 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
.AndReturn(default_role)
|
||||
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
|
||||
.AndReturn(users)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(roles)
|
||||
|
||||
workflow_data = {}
|
||||
|
||||
@ -857,8 +843,8 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
user.id,
|
||||
self.tenant.id).AndReturn(roles)
|
||||
|
||||
workflow_data["role_1"] = ['1', '3'] # admin role
|
||||
workflow_data["role_2"] = ['1', '2', '3'] # member role
|
||||
workflow_data[USER_ROLE_PREFIX + "1"] = ['1', '3'] # admin role
|
||||
workflow_data[USER_ROLE_PREFIX + "2"] = ['1', '2', '3'] # member role
|
||||
|
||||
# update some fields
|
||||
project._info["name"] = "updated name"
|
||||
@ -871,17 +857,12 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
"enabled": project.enabled}
|
||||
updated_quota = self._get_quota_info(quota)
|
||||
|
||||
# contribute
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
|
||||
# handle
|
||||
# handle
|
||||
api.keystone.tenant_update(IsA(http.HttpRequest),
|
||||
project.id,
|
||||
**updated_project) \
|
||||
.AndReturn(project)
|
||||
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.user_list(IsA(http.HttpRequest),
|
||||
project=self.tenant.id).AndReturn(proj_users)
|
||||
|
||||
@ -962,18 +943,20 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
.AndReturn(quota)
|
||||
|
||||
api.keystone.get_default_role(IsA(http.HttpRequest)) \
|
||||
.AndReturn(default_role)
|
||||
.MultipleTimes().AndReturn(default_role)
|
||||
api.keystone.user_list(IsA(http.HttpRequest), domain=domain_id) \
|
||||
.AndReturn(users)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.role_list(IsA(http.HttpRequest)) \
|
||||
.MultipleTimes().AndReturn(roles)
|
||||
|
||||
workflow_data = {}
|
||||
for user in users:
|
||||
api.keystone.roles_for_user(IsA(http.HttpRequest),
|
||||
user.id,
|
||||
self.tenant.id).AndReturn(roles)
|
||||
workflow_data["role_1"] = ['1', '3'] # admin role
|
||||
workflow_data["role_2"] = ['1', '2', '3'] # member role
|
||||
|
||||
workflow_data[USER_ROLE_PREFIX + "1"] = ['1', '3'] # admin role
|
||||
workflow_data[USER_ROLE_PREFIX + "2"] = ['1', '2', '3'] # member role
|
||||
|
||||
# update some fields
|
||||
project._info["name"] = "updated name"
|
||||
@ -986,16 +969,12 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
"enabled": project.enabled}
|
||||
updated_quota = self._get_quota_info(quota)
|
||||
|
||||
# contribute
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
|
||||
# handle
|
||||
api.keystone.tenant_update(IsA(http.HttpRequest),
|
||||
project.id,
|
||||
**updated_project) \
|
||||
.AndReturn(project)
|
||||
|
||||
api.keystone.role_list(IsA(http.HttpRequest)).AndReturn(roles)
|
||||
api.keystone.user_list(IsA(http.HttpRequest),
|
||||
project=self.tenant.id).AndReturn(proj_users)
|
||||
|
||||
@ -1047,7 +1026,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
|
||||
quota = self.quotas.first()
|
||||
|
||||
api.keystone.get_default_role(IsA(http.HttpRequest)) \
|
||||
.AndReturn(None) # Default role doesn't exist
|
||||
.MultipleTimes().AndReturn(None) # Default role doesn't exist
|
||||
api.keystone.tenant_get(IsA(http.HttpRequest), self.tenant.id,
|
||||
admin=True) \
|
||||
.AndReturn(project)
|
||||
|
@ -23,7 +23,6 @@ from django.conf.urls.defaults import url
|
||||
|
||||
from openstack_dashboard.dashboards.admin.projects.views \
|
||||
import CreateProjectView
|
||||
from openstack_dashboard.dashboards.admin.projects.views import CreateUserView
|
||||
from openstack_dashboard.dashboards.admin.projects.views import IndexView
|
||||
from openstack_dashboard.dashboards.admin.projects.views \
|
||||
import ProjectUsageView
|
||||
@ -38,6 +37,4 @@ urlpatterns = patterns('',
|
||||
UpdateProjectView.as_view(), name='update'),
|
||||
url(r'^(?P<tenant_id>[^/]+)/usage/$',
|
||||
ProjectUsageView.as_view(), name='usage'),
|
||||
url(r'^(?P<tenant_id>[^/]+)/create_user/$',
|
||||
CreateUserView.as_view(), name='create_user'),
|
||||
)
|
||||
|
@ -21,7 +21,6 @@
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
@ -29,15 +28,10 @@ from horizon import tables
|
||||
from horizon import workflows
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.dashboards.admin.users.views import CreateView
|
||||
from openstack_dashboard import usage
|
||||
from openstack_dashboard.usage import quotas
|
||||
|
||||
from openstack_dashboard.dashboards.admin.projects.forms import CreateUser
|
||||
from openstack_dashboard.dashboards.admin.projects.tables import AddUsersTable
|
||||
from openstack_dashboard.dashboards.admin.projects.tables import TenantsTable
|
||||
from openstack_dashboard.dashboards.admin.projects.tables \
|
||||
import TenantUsersTable
|
||||
from openstack_dashboard.dashboards.admin.projects.workflows \
|
||||
import CreateProject
|
||||
from openstack_dashboard.dashboards.admin.projects.workflows \
|
||||
@ -97,45 +91,6 @@ class IndexView(tables.DataTableView):
|
||||
return tenants
|
||||
|
||||
|
||||
class UsersView(tables.MultiTableView):
|
||||
table_classes = (TenantUsersTable, AddUsersTable)
|
||||
template_name = 'admin/projects/users.html'
|
||||
|
||||
def _get_shared_data(self, *args, **kwargs):
|
||||
tenant_id = self.kwargs["tenant_id"]
|
||||
if not hasattr(self, "_shared_data"):
|
||||
domain_context = self.request.session.get('domain_context', None)
|
||||
try:
|
||||
tenant = api.keystone.tenant_get(self.request,
|
||||
tenant_id,
|
||||
admin=True)
|
||||
all_users = api.keystone.user_list(self.request,
|
||||
domain=domain_context)
|
||||
tenant_users = api.keystone.user_list(self.request, tenant_id)
|
||||
self._shared_data = {'tenant': tenant,
|
||||
'all_users': all_users,
|
||||
'tenant_users': tenant_users}
|
||||
except:
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to retrieve users."),
|
||||
redirect=reverse(INDEX_URL))
|
||||
return self._shared_data
|
||||
|
||||
def get_tenant_users_data(self):
|
||||
return self._get_shared_data()["tenant_users"]
|
||||
|
||||
def get_add_users_data(self):
|
||||
tenant_users = self._get_shared_data()["tenant_users"]
|
||||
all_users = self._get_shared_data()["all_users"]
|
||||
tenant_user_ids = [user.id for user in tenant_users]
|
||||
return filter(lambda u: u.id not in tenant_user_ids, all_users)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UsersView, self).get_context_data(**kwargs)
|
||||
context['tenant'] = self._get_shared_data()["tenant"]
|
||||
return context
|
||||
|
||||
|
||||
class ProjectUsageView(usage.UsageView):
|
||||
table_class = usage.ProjectUsageTable
|
||||
usage_class = usage.ProjectUsage
|
||||
@ -191,23 +146,3 @@ class UpdateProjectView(workflows.WorkflowView):
|
||||
_('Unable to retrieve project details.'),
|
||||
redirect=reverse(INDEX_URL))
|
||||
return initial
|
||||
|
||||
|
||||
class CreateUserView(CreateView):
|
||||
form_class = CreateUser
|
||||
template_name = "admin/projects/create_user.html"
|
||||
success_url = reverse_lazy('horizon:admin:projects:index')
|
||||
|
||||
def get_initial(self):
|
||||
default_role = api.keystone.get_default_role(self.request)
|
||||
return {'role_id': getattr(default_role, "id", None),
|
||||
'project': self.kwargs['tenant_id']}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateUserView, self).get_context_data(**kwargs)
|
||||
context['tenant_id'] = self.kwargs['tenant_id']
|
||||
context['tenant_name'] = api.keystone.tenant_get(
|
||||
self.request,
|
||||
self.kwargs['tenant_id'],
|
||||
admin=True).name
|
||||
return context
|
||||
|
@ -39,6 +39,7 @@ from openstack_dashboard.usage.quotas import QUOTA_FIELDS
|
||||
|
||||
INDEX_URL = "horizon:admin:projects:index"
|
||||
ADD_USER_URL = "horizon:admin:projects:create_user"
|
||||
PROJECT_USER_MEMBER_SLUG = "update_members"
|
||||
|
||||
|
||||
class UpdateProjectQuotaAction(workflows.Action):
|
||||
@ -108,9 +109,7 @@ class CreateProjectInfo(workflows.Step):
|
||||
"enabled")
|
||||
|
||||
|
||||
class UpdateProjectMembersAction(workflows.Action):
|
||||
default_role = forms.CharField(required=False)
|
||||
|
||||
class UpdateProjectMembersAction(workflows.MembershipAction):
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(UpdateProjectMembersAction, self).__init__(request,
|
||||
*args,
|
||||
@ -134,7 +133,9 @@ class UpdateProjectMembersAction(workflows.Action):
|
||||
exceptions.handle(self.request,
|
||||
err_msg,
|
||||
redirect=reverse(INDEX_URL))
|
||||
self.fields['default_role'].initial = default_role.id
|
||||
default_role_name = self.get_default_role_field_name()
|
||||
self.fields[default_role_name] = forms.CharField(required=False)
|
||||
self.fields[default_role_name].initial = default_role.id
|
||||
|
||||
# Get list of available users
|
||||
all_users = []
|
||||
@ -155,7 +156,7 @@ class UpdateProjectMembersAction(workflows.Action):
|
||||
err_msg,
|
||||
redirect=reverse(INDEX_URL))
|
||||
for role in role_list:
|
||||
field_name = "role_" + role.id
|
||||
field_name = self.get_member_field_name(role.id)
|
||||
label = _(role.name)
|
||||
self.fields[field_name] = forms.MultipleChoiceField(required=False,
|
||||
label=label)
|
||||
@ -174,11 +175,12 @@ class UpdateProjectMembersAction(workflows.Action):
|
||||
err_msg,
|
||||
redirect=reverse(INDEX_URL))
|
||||
for role in roles:
|
||||
self.fields["role_" + role.id].initial.append(user.id)
|
||||
field_name = self.get_member_field_name(role.id)
|
||||
self.fields[field_name].initial.append(user.id)
|
||||
|
||||
class Meta:
|
||||
name = _("Project Members")
|
||||
slug = "update_members"
|
||||
slug = PROJECT_USER_MEMBER_SLUG
|
||||
|
||||
|
||||
class UpdateProjectMembers(workflows.UpdateMembersStep):
|
||||
@ -198,7 +200,7 @@ class UpdateProjectMembers(workflows.UpdateMembersStep):
|
||||
|
||||
post = self.workflow.request.POST
|
||||
for role in roles:
|
||||
field = "role_" + role.id
|
||||
field = self.get_member_field_name(role.id)
|
||||
context[field] = post.getlist(field)
|
||||
return context
|
||||
|
||||
@ -214,6 +216,14 @@ class CreateProject(workflows.Workflow):
|
||||
UpdateProjectMembers,
|
||||
UpdateProjectQuota)
|
||||
|
||||
def __init__(self, request=None, context_seed=None, entry_point=None,
|
||||
*args, **kwargs):
|
||||
super(CreateProject, self).__init__(request=request,
|
||||
context_seed=context_seed,
|
||||
entry_point=entry_point,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
def format_status_message(self, message):
|
||||
return message % self.context.get('name', 'unknown project')
|
||||
|
||||
@ -237,14 +247,16 @@ class CreateProject(workflows.Workflow):
|
||||
users_to_add = 0
|
||||
try:
|
||||
available_roles = api.keystone.role_list(request)
|
||||
|
||||
member_step = self.get_step(PROJECT_USER_MEMBER_SLUG)
|
||||
# count how many users are to be added
|
||||
for role in available_roles:
|
||||
role_list = data["role_" + role.id]
|
||||
field_name = member_step.get_member_field_name(role.id)
|
||||
role_list = data[field_name]
|
||||
users_to_add += len(role_list)
|
||||
# add new users to project
|
||||
for role in available_roles:
|
||||
role_list = data["role_" + role.id]
|
||||
field_name = member_step.get_member_field_name(role.id)
|
||||
role_list = data[field_name]
|
||||
users_added = 0
|
||||
for user in role_list:
|
||||
api.keystone.add_tenant_user_role(request,
|
||||
@ -255,8 +267,8 @@ class CreateProject(workflows.Workflow):
|
||||
users_to_add -= users_added
|
||||
except:
|
||||
exceptions.handle(request, _('Failed to add %s project members '
|
||||
'and set project quotas.'
|
||||
% users_to_add))
|
||||
'and set project quotas.')
|
||||
% users_to_add)
|
||||
|
||||
# Update the project quota.
|
||||
nova_data = dict([(key, data[key]) for key in NOVA_QUOTA_FIELDS])
|
||||
@ -302,6 +314,14 @@ class UpdateProject(workflows.Workflow):
|
||||
UpdateProjectMembers,
|
||||
UpdateProjectQuota)
|
||||
|
||||
def __init__(self, request=None, context_seed=None, entry_point=None,
|
||||
*args, **kwargs):
|
||||
super(UpdateProject, self).__init__(request=request,
|
||||
context_seed=context_seed,
|
||||
entry_point=entry_point,
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
def format_status_message(self, message):
|
||||
return message % self.context.get('name', 'unknown project')
|
||||
|
||||
@ -322,12 +342,14 @@ class UpdateProject(workflows.Workflow):
|
||||
exceptions.handle(request, ignore=True)
|
||||
return False
|
||||
|
||||
# Get our role options
|
||||
available_roles = api.keystone.role_list(request)
|
||||
|
||||
# update project members
|
||||
users_to_modify = 0
|
||||
# Project-user member step
|
||||
member_step = self.get_step(PROJECT_USER_MEMBER_SLUG)
|
||||
try:
|
||||
# Get our role options
|
||||
available_roles = api.keystone.role_list(request)
|
||||
|
||||
# Get the users currently associated with this project so we
|
||||
# can diff against it.
|
||||
project_members = api.keystone.user_list(request,
|
||||
@ -341,9 +363,11 @@ class UpdateProject(workflows.Workflow):
|
||||
user.id,
|
||||
project_id)
|
||||
current_role_ids = [role.id for role in current_roles]
|
||||
|
||||
for role in available_roles:
|
||||
field_name = member_step.get_member_field_name(role.id)
|
||||
# Check if the user is in the list of users with this role.
|
||||
if user.id in data["role_" + role.id]:
|
||||
if user.id in data[field_name]:
|
||||
# Add it if necessary
|
||||
if role.id not in current_role_ids:
|
||||
# user role has changed
|
||||
@ -389,11 +413,13 @@ class UpdateProject(workflows.Workflow):
|
||||
|
||||
# Grant new roles on the project.
|
||||
for role in available_roles:
|
||||
field_name = member_step.get_member_field_name(role.id)
|
||||
# Count how many users may be added for exception handling.
|
||||
users_to_modify += len(data["role_" + role.id])
|
||||
users_to_modify += len(data[field_name])
|
||||
for role in available_roles:
|
||||
users_added = 0
|
||||
for user_id in data["role_" + role.id]:
|
||||
field_name = member_step.get_member_field_name(role.id)
|
||||
for user_id in data[field_name]:
|
||||
if not filter(lambda x: user_id == x.id, project_members):
|
||||
api.keystone.add_tenant_user_role(request,
|
||||
project=project_id,
|
||||
@ -403,8 +429,8 @@ class UpdateProject(workflows.Workflow):
|
||||
users_to_modify -= users_added
|
||||
except:
|
||||
exceptions.handle(request, _('Failed to modify %s project members '
|
||||
'and update project quotas.'
|
||||
% users_to_modify))
|
||||
'and update project quotas.')
|
||||
% users_to_modify)
|
||||
return True
|
||||
|
||||
# update the project quota
|
||||
|
@ -39,11 +39,14 @@ from openstack_dashboard.dashboards.project.instances.tabs \
|
||||
import InstanceDetailTabs
|
||||
from openstack_dashboard.dashboards.project.instances.workflows \
|
||||
import LaunchInstance
|
||||
from openstack_dashboard.dashboards.project.instances.workflows \
|
||||
import update_instance
|
||||
|
||||
from novaclient.v1_1.servers import REBOOT_HARD
|
||||
from novaclient.v1_1.servers import REBOOT_SOFT
|
||||
|
||||
INDEX_URL = reverse('horizon:project:instances:index')
|
||||
SEC_GROUP_ROLE_PREFIX = update_instance.INSTANCE_SEC_GROUP_SLUG + "_role_"
|
||||
|
||||
|
||||
class InstanceTests(test.TestCase):
|
||||
@ -723,9 +726,11 @@ class InstanceTests(test.TestCase):
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
def _instance_update_post(self, server_id, server_name, secgroups):
|
||||
default_role_field_name = 'default_' + \
|
||||
update_instance.INSTANCE_SEC_GROUP_SLUG + '_role'
|
||||
formData = {'name': server_name,
|
||||
'default_role': 'member',
|
||||
'role_member': secgroups}
|
||||
default_role_field_name: 'member',
|
||||
SEC_GROUP_ROLE_PREFIX + 'member': secgroups}
|
||||
url = reverse('horizon:project:instances:update',
|
||||
args=[server_id])
|
||||
return self.client.post(url, formData)
|
||||
|
@ -28,15 +28,12 @@ from horizon import workflows
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.utils.filters import get_int_or_uuid
|
||||
|
||||
|
||||
INDEX_URL = "horizon:projects:instances:index"
|
||||
ADD_USER_URL = "horizon:projects:instances:create_user"
|
||||
INSTANCE_SEC_GROUP_SLUG = "update_security_groups"
|
||||
|
||||
|
||||
class UpdateInstanceSecurityGroupsAction(workflows.Action):
|
||||
default_role = forms.CharField(required=False)
|
||||
role_member = forms.MultipleChoiceField(required=False)
|
||||
|
||||
class UpdateInstanceSecurityGroupsAction(workflows.MembershipAction):
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(UpdateInstanceSecurityGroupsAction, self).__init__(request,
|
||||
*args,
|
||||
@ -46,7 +43,9 @@ class UpdateInstanceSecurityGroupsAction(workflows.Action):
|
||||
context = args[0]
|
||||
instance_id = context.get('instance_id', '')
|
||||
|
||||
self.fields['default_role'].initial = 'member'
|
||||
default_role_name = self.get_default_role_field_name()
|
||||
self.fields[default_role_name] = forms.CharField(required=False)
|
||||
self.fields[default_role_name].initial = 'member'
|
||||
|
||||
# Get list of available security groups
|
||||
all_groups = []
|
||||
@ -62,9 +61,11 @@ class UpdateInstanceSecurityGroupsAction(workflows.Action):
|
||||
instance_id)
|
||||
except Exception:
|
||||
exceptions.handle(request, err_msg)
|
||||
self.fields['role_member'].choices = groups_list
|
||||
self.fields['role_member'].initial = [group.id
|
||||
for group in instance_groups]
|
||||
field_name = self.get_member_field_name('member')
|
||||
self.fields[field_name] = forms.MultipleChoiceField(required=False)
|
||||
self.fields[field_name].choices = groups_list
|
||||
self.fields[field_name].initial = [group.id
|
||||
for group in instance_groups]
|
||||
|
||||
def handle(self, request, data):
|
||||
instance_id = data['instance_id']
|
||||
@ -79,7 +80,7 @@ class UpdateInstanceSecurityGroupsAction(workflows.Action):
|
||||
|
||||
class Meta:
|
||||
name = _("Security Groups")
|
||||
slug = "update_security_groups"
|
||||
slug = INSTANCE_SEC_GROUP_SLUG
|
||||
|
||||
|
||||
class UpdateInstanceSecurityGroups(workflows.UpdateMembersStep):
|
||||
@ -97,7 +98,8 @@ class UpdateInstanceSecurityGroups(workflows.UpdateMembersStep):
|
||||
def contribute(self, data, context):
|
||||
request = self.workflow.request
|
||||
if data:
|
||||
context["wanted_groups"] = request.POST.getlist("role_member")
|
||||
field_name = self.get_member_field_name('member')
|
||||
context["wanted_groups"] = request.POST.getlist(field_name)
|
||||
return context
|
||||
|
||||
|
||||
|
@ -1367,8 +1367,8 @@ label.log-length {
|
||||
margin-top: -60px;
|
||||
}
|
||||
|
||||
/* Project Membership UI */
|
||||
.project_membership {
|
||||
/* Membership widget UI */
|
||||
.membership {
|
||||
min-height: 200px;
|
||||
|
||||
/* Buttons */
|
||||
@ -1391,7 +1391,7 @@ label.log-length {
|
||||
margin-left: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.users_title {
|
||||
.members_title {
|
||||
color: #555;
|
||||
font-weight: bold;
|
||||
padding-left: 10px;
|
||||
@ -1435,16 +1435,16 @@ label.log-length {
|
||||
}
|
||||
}
|
||||
|
||||
/* User lists */
|
||||
/* Member lists */
|
||||
.member {
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
.project_members {
|
||||
.members {
|
||||
margin-left: -20px;
|
||||
}
|
||||
.project_members ul.btn-group,
|
||||
.available_users ul.btn-group {
|
||||
.members ul.btn-group,
|
||||
.available_members ul.btn-group {
|
||||
width: 308px;
|
||||
}
|
||||
.dark_stripe {
|
||||
@ -1505,14 +1505,14 @@ label.log-length {
|
||||
}
|
||||
}
|
||||
|
||||
/* Inline user creation */
|
||||
.add_user_btn {
|
||||
/* Inline member creation */
|
||||
.add_member_btn {
|
||||
display: inline;
|
||||
}
|
||||
#add_user {
|
||||
#add_member {
|
||||
clear: both;
|
||||
}
|
||||
.add_user {
|
||||
.add_member {
|
||||
float: right;
|
||||
margin-top: 10px;
|
||||
margin-right: 15px;
|
||||
|
Loading…
Reference in New Issue
Block a user