Group CRUD and Management in Admin Dashboard.
Add support for CRUD on Group for admin users. It also includes the user management capability for the group. Keystone revokes the token when a user is added or removed from the group. If the logon user is added/removed from the group, the user will be redirected to the login page. This feature is only exposed if the user explicitly set horizon for keystone V3. Implements blueprint admin-group-crud Change-Id: I1b5456af80bcc35e9d16ac6ba6792e82704e76fd
This commit is contained in:
parent
65525a15f5
commit
fa32d84b0e
|
@ -20,3 +20,5 @@
|
|||
<div class="modal-footer">{% block modal-footer %}{% endblock %}</div>
|
||||
</form>
|
||||
</div>
|
||||
{% block modal-js %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,41 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body %}
|
||||
<input type="hidden" id="hidden_redirect_back_to_home" name="redirect" value="{{redirect}}"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-js %}
|
||||
<script type="text/javascript">
|
||||
<!--
|
||||
/* ensure the value of the action buttons also gets submitted */
|
||||
$(':button[name="action"]').click(function () {
|
||||
value = $(this).attr("value");
|
||||
|
||||
$('<input>').attr({
|
||||
type: 'hidden',
|
||||
name: 'action',
|
||||
value: value,
|
||||
}).appendTo('form');
|
||||
|
||||
var redirect = $('#hidden_redirect_back_to_home').val();
|
||||
$('<input>').attr({
|
||||
type: 'hidden',
|
||||
name: 'redirect',
|
||||
value: redirect,
|
||||
}).appendTo('form');
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
/* add the correct ajax class for table footer links */
|
||||
$("tfoot a").addClass("ajax-modal");
|
||||
|
||||
/* as you navigate around the more pages, they stack on top of each other
|
||||
make sure this stack gets cleaned up so there is only on*/
|
||||
hidden_modals = $("#modal_wrapper .modal.hide:not(:last-child)");
|
||||
hidden_modals.detach();
|
||||
|
||||
//-->
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -360,6 +360,45 @@ def user_update_tenant(request, user, project, admin=True):
|
|||
return manager.update(user, project=project)
|
||||
|
||||
|
||||
def group_create(request, domain_id, name, description=None):
|
||||
manager = keystoneclient(request, admin=True).groups
|
||||
return manager.create(domain=domain_id,
|
||||
name=name,
|
||||
description=description)
|
||||
|
||||
|
||||
def group_get(request, group_id, admin=True):
|
||||
manager = keystoneclient(request, admin=admin).groups
|
||||
return manager.get(group_id)
|
||||
|
||||
|
||||
def group_delete(request, group_id):
|
||||
manager = keystoneclient(request, admin=True).groups
|
||||
return manager.delete(group_id)
|
||||
|
||||
|
||||
def group_list(request):
|
||||
manager = keystoneclient(request, admin=True).groups
|
||||
return manager.list()
|
||||
|
||||
|
||||
def group_update(request, group_id, name=None, description=None):
|
||||
manager = keystoneclient(request, admin=True).groups
|
||||
return manager.update(group=group_id,
|
||||
name=name,
|
||||
description=description)
|
||||
|
||||
|
||||
def add_group_user(request, group_id, user_id):
|
||||
manager = keystoneclient(request, admin=True).users
|
||||
return manager.add_to_group(group=group_id, user=user_id)
|
||||
|
||||
|
||||
def remove_group_user(request, group_id, user_id):
|
||||
manager = keystoneclient(request, admin=True).users
|
||||
return manager.remove_from_group(group=group_id, user=user_id)
|
||||
|
||||
|
||||
def role_create(request, name):
|
||||
manager = keystoneclient(request, admin=True).roles
|
||||
return manager.create(name)
|
||||
|
@ -472,6 +511,11 @@ def keystone_can_edit_project():
|
|||
return backend_settings.get('can_edit_project', True)
|
||||
|
||||
|
||||
def keystone_can_edit_group():
|
||||
backend_settings = getattr(settings, "OPENSTACK_KEYSTONE_BACKEND", {})
|
||||
return backend_settings.get('can_edit_group', True)
|
||||
|
||||
|
||||
def keystone_can_edit_role():
|
||||
backend_settings = getattr(settings, "OPENSTACK_KEYSTONE_BACKEND", {})
|
||||
return backend_settings.get('can_edit_role', True)
|
||||
|
|
|
@ -23,8 +23,8 @@ class SystemPanels(horizon.PanelGroup):
|
|||
slug = "admin"
|
||||
name = _("System Panel")
|
||||
panels = ('overview', 'instances', 'volumes', 'flavors',
|
||||
'images', 'domains', 'projects', 'users', 'roles',
|
||||
'networks', 'routers', 'info')
|
||||
'images', 'domains', 'projects', 'users', 'groups',
|
||||
'roles', 'networks', 'routers', 'info')
|
||||
|
||||
|
||||
class Admin(horizon.Dashboard):
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
GROUPS_INDEX_URL = 'horizon:admin:groups:index'
|
||||
GROUPS_INDEX_VIEW_TEMPLATE = 'admin/groups/index.html'
|
||||
GROUPS_CREATE_URL = 'horizon:admin:groups:create'
|
||||
GROUPS_CREATE_VIEW_TEMPLATE = 'admin/groups/create.html'
|
||||
GROUPS_UPDATE_URL = 'horizon:admin:groups:update'
|
||||
GROUPS_UPDATE_VIEW_TEMPLATE = 'admin/groups/update.html'
|
||||
GROUPS_MANAGE_URL = 'horizon:admin:groups:manage_members'
|
||||
GROUPS_MANAGE_VIEW_TEMPLATE = 'admin/groups/manage.html'
|
||||
GROUPS_ADD_MEMBER_URL = 'horizon:admin:groups:add_members'
|
||||
GROUPS_ADD_MEMBER_VIEW_TEMPLATE = 'admin/groups/add_non_member.html'
|
||||
GROUPS_ADD_MEMBER_AJAX_VIEW_TEMPLATE = 'admin/groups/_add_non_member.html'
|
|
@ -0,0 +1,77 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateGroupForm(forms.SelfHandlingForm):
|
||||
name = forms.CharField(label=_("Name"),
|
||||
required=True)
|
||||
description = forms.CharField(widget=forms.widgets.Textarea(),
|
||||
label=_("Description"),
|
||||
required=False)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.info('Creating group with name "%s"' % data['name'])
|
||||
# TODO: Set the domain_id with the value from the Domain scope.
|
||||
new_group = api.keystone.group_create(
|
||||
request,
|
||||
domain_id=None,
|
||||
name=data['name'],
|
||||
description=data['description'])
|
||||
messages.success(request,
|
||||
_('Group "%s" was successfully created.')
|
||||
% data['name'])
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to create group.'))
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class UpdateGroupForm(forms.SelfHandlingForm):
|
||||
group_id = forms.CharField(widget=forms.HiddenInput())
|
||||
name = forms.CharField(label=_("Name"),
|
||||
required=True)
|
||||
description = forms.CharField(widget=forms.widgets.Textarea(),
|
||||
label=_("Description"),
|
||||
required=False)
|
||||
|
||||
def handle(self, request, data):
|
||||
group_id = data.pop('group_id')
|
||||
|
||||
try:
|
||||
api.keystone.group_update(request,
|
||||
group_id=group_id,
|
||||
name=data['name'],
|
||||
description=data['description'])
|
||||
messages.success(request,
|
||||
_('Group has been updated successfully.'))
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to update the group.'))
|
||||
return False
|
||||
return True
|
|
@ -0,0 +1,31 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
|
||||
from openstack_dashboard.api.keystone import VERSIONS as IDENTITY_VERSIONS
|
||||
from openstack_dashboard.dashboards.admin import dashboard
|
||||
|
||||
|
||||
class Groups(horizon.Panel):
|
||||
name = _("Groups")
|
||||
slug = 'groups'
|
||||
|
||||
|
||||
if IDENTITY_VERSIONS.active >= 3:
|
||||
dashboard.Admin.register(Groups)
|
|
@ -0,0 +1,206 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template import defaultfilters
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
from .constants import GROUPS_CREATE_URL, GROUPS_UPDATE_URL, \
|
||||
GROUPS_MANAGE_URL, GROUPS_ADD_MEMBER_URL
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
LOGOUT_URL = 'logout'
|
||||
STATUS_CHOICES = (
|
||||
("true", True),
|
||||
("false", False)
|
||||
)
|
||||
|
||||
|
||||
class CreateGroupLink(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Group")
|
||||
url = GROUPS_CREATE_URL
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
def allowed(self, request, group):
|
||||
return api.keystone.keystone_can_edit_group()
|
||||
|
||||
|
||||
class EditGroupLink(tables.LinkAction):
|
||||
name = "edit"
|
||||
verbose_name = _("Edit Group")
|
||||
url = GROUPS_UPDATE_URL
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def allowed(self, request, group):
|
||||
return api.keystone.keystone_can_edit_group()
|
||||
|
||||
|
||||
class DeleteGroupsAction(tables.DeleteAction):
|
||||
name = "delete"
|
||||
data_type_singular = _("Group")
|
||||
data_type_plural = _("Groups")
|
||||
|
||||
def allowed(self, request, datum):
|
||||
return api.keystone.keystone_can_edit_group()
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
LOG.info('Deleting group "%s".' % obj_id)
|
||||
api.keystone.group_delete(request, obj_id)
|
||||
|
||||
|
||||
class ManageUsersLink(tables.LinkAction):
|
||||
name = "users"
|
||||
verbose_name = _("Modify Users")
|
||||
url = GROUPS_MANAGE_URL
|
||||
classes = ("btn-edit")
|
||||
|
||||
def allowed(self, request, datum):
|
||||
return api.keystone.keystone_can_edit_group()
|
||||
|
||||
|
||||
class GroupFilterAction(tables.FilterAction):
|
||||
def filter(self, table, groups, filter_string):
|
||||
""" Naive case-insensitive search """
|
||||
q = filter_string.lower()
|
||||
|
||||
def comp(group):
|
||||
if q in group.name.lower():
|
||||
return True
|
||||
return False
|
||||
|
||||
return filter(comp, groups)
|
||||
|
||||
|
||||
class GroupsTable(tables.DataTable):
|
||||
name = tables.Column('name', verbose_name=_('Name'))
|
||||
description = tables.Column(lambda obj: getattr(obj, 'description', None),
|
||||
verbose_name=_('Description'))
|
||||
id = tables.Column('id', verbose_name=_('Group ID'))
|
||||
|
||||
class Meta:
|
||||
name = "groups"
|
||||
verbose_name = _("Groups")
|
||||
row_actions = (ManageUsersLink, EditGroupLink, DeleteGroupsAction)
|
||||
table_actions = (GroupFilterAction, CreateGroupLink,
|
||||
DeleteGroupsAction)
|
||||
|
||||
|
||||
class UserFilterAction(tables.FilterAction):
|
||||
def filter(self, table, users, filter_string):
|
||||
""" Naive case-insensitive search """
|
||||
q = filter_string.lower()
|
||||
return [user for user in users
|
||||
if q in user.name.lower()
|
||||
or q in user.email.lower()]
|
||||
|
||||
|
||||
class RemoveMembers(tables.DeleteAction):
|
||||
name = "removeGroupMember"
|
||||
action_present = _("Remove")
|
||||
action_past = _("Removed")
|
||||
data_type_singular = _("User")
|
||||
data_type_plural = _("Users")
|
||||
|
||||
def allowed(self, request, user=None):
|
||||
return api.keystone.keystone_can_edit_group()
|
||||
|
||||
def action(self, request, obj_id):
|
||||
user_obj = self.table.get_object_by_id(obj_id)
|
||||
group_id = self.table.kwargs['group_id']
|
||||
LOG.info('Removing user %s from group %s.' % (user_obj.id,
|
||||
group_id))
|
||||
api.keystone.remove_group_user(request,
|
||||
group_id=group_id,
|
||||
user_id=user_obj.id)
|
||||
# TODO: Fix the bug when removing current user
|
||||
# Keystone revokes the token of the user removed from the group.
|
||||
# If the logon user was removed, redirect the user to logout.
|
||||
|
||||
|
||||
class AddMembersLink(tables.LinkAction):
|
||||
name = "add_user_link"
|
||||
verbose_name = _("Add...")
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
url = GROUPS_ADD_MEMBER_URL
|
||||
|
||||
def allowed(self, request, user=None):
|
||||
return api.keystone.keystone_can_edit_group()
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
return reverse(self.url, kwargs=self.table.kwargs)
|
||||
|
||||
|
||||
class UsersTable(tables.DataTable):
|
||||
name = tables.Column('name', verbose_name=_('User Name'))
|
||||
email = tables.Column('email', verbose_name=_('Email'),
|
||||
filters=[defaultfilters.urlize])
|
||||
id = tables.Column('id', verbose_name=_('User ID'))
|
||||
enabled = tables.Column('enabled', verbose_name=_('Enabled'),
|
||||
status=True,
|
||||
status_choices=STATUS_CHOICES,
|
||||
empty_value="False")
|
||||
|
||||
|
||||
class GroupMembersTable(UsersTable):
|
||||
class Meta:
|
||||
name = "group_members"
|
||||
verbose_name = _("Group Members")
|
||||
table_actions = (UserFilterAction, AddMembersLink, RemoveMembers)
|
||||
|
||||
|
||||
class AddMembers(tables.BatchAction):
|
||||
name = "addMember"
|
||||
action_present = _("Add")
|
||||
action_past = _("Added")
|
||||
data_type_singular = _("User")
|
||||
data_type_plural = _("Users")
|
||||
classes = ("btn-create", )
|
||||
requires_input = True
|
||||
success_url = GROUPS_MANAGE_URL
|
||||
|
||||
def allowed(self, request, user=None):
|
||||
return api.keystone.keystone_can_edit_group()
|
||||
|
||||
def action(self, request, obj_id):
|
||||
user_obj = self.table.get_object_by_id(obj_id)
|
||||
group_id = self.table.kwargs['group_id']
|
||||
LOG.info('Adding user %s to group %s.' % (user_obj.id,
|
||||
group_id))
|
||||
api.keystone.add_group_user(request,
|
||||
group_id=group_id,
|
||||
user_id=user_obj.id)
|
||||
# TODO: Fix the bug when adding current user
|
||||
# Keystone revokes the token of the user added to the group.
|
||||
# If the logon user was added, redirect the user to logout.
|
||||
|
||||
def get_success_url(self, request=None):
|
||||
group_id = self.table.kwargs.get('group_id', None)
|
||||
return reverse(self.success_url, args=[group_id])
|
||||
|
||||
|
||||
class GroupNonMembersTable(UsersTable):
|
||||
class Meta:
|
||||
name = "group_non_members"
|
||||
verbose_name = _("Non-Members")
|
||||
table_actions = (UserFilterAction, AddMembers)
|
|
@ -0,0 +1,9 @@
|
|||
{% extends "horizon/common/_modal_form_add_members.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block modal-header %}{% trans "Add Group Assignment" %}{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<a href="{% url 'horizon:admin:groups:manage_members' group.id %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
|
@ -0,0 +1,25 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}create_group_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:groups:create' %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Create Group" %}{% 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 group to organize users and roles." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Group" %}" />
|
||||
<a href="{% url 'horizon:admin:groups:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
|
@ -0,0 +1,25 @@
|
|||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}update_group_form{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:admin:groups:update' group.id %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Update Group" %}{% 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 the group's details." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Update Group" %}" />
|
||||
<a href="{% url 'horizon:admin:groups:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
|
@ -0,0 +1,7 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans 'Add User to Group' %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'admin/groups/_add_non_member.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Group" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Group") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'admin/groups/_create.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Groups" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Groups") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans 'Group Management' %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Group Management: ")|add:group.name %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{{ group_members_table.render }}
|
||||
{% endblock %}
|
|
@ -0,0 +1,11 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Update Group" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Update Group") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'admin/groups/_update.html' %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,196 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 django import http
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from mox import IgnoreArg, IsA
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
from .constants import GROUPS_INDEX_VIEW_TEMPLATE, \
|
||||
GROUPS_MANAGE_VIEW_TEMPLATE, \
|
||||
GROUPS_INDEX_URL as index_url, \
|
||||
GROUPS_CREATE_URL as create_url, \
|
||||
GROUPS_UPDATE_URL as update_url, \
|
||||
GROUPS_MANAGE_URL as manage_url, \
|
||||
GROUPS_ADD_MEMBER_URL as add_member_url
|
||||
|
||||
|
||||
GROUPS_INDEX_URL = reverse(index_url)
|
||||
GROUP_CREATE_URL = reverse(create_url)
|
||||
GROUP_UPDATE_URL = reverse(update_url, args=[1])
|
||||
GROUP_MANAGE_URL = reverse(manage_url, args=[1])
|
||||
GROUP_ADD_MEMBER_URL = reverse(add_member_url, args=[1])
|
||||
|
||||
|
||||
class GroupsViewTests(test.BaseAdminViewTests):
|
||||
@test.create_stubs({api.keystone: ('group_list',)})
|
||||
def test_index(self):
|
||||
api.keystone.group_list(IgnoreArg()).AndReturn(self.groups.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(GROUPS_INDEX_URL)
|
||||
|
||||
self.assertTemplateUsed(res, GROUPS_INDEX_VIEW_TEMPLATE)
|
||||
self.assertItemsEqual(res.context['table'].data, self.groups.list())
|
||||
|
||||
self.assertContains(res, 'Create Group')
|
||||
self.assertContains(res, 'Edit')
|
||||
self.assertContains(res, 'Delete Group')
|
||||
|
||||
@test.create_stubs({api.keystone: ('group_list',
|
||||
'keystone_can_edit_group')})
|
||||
def test_index_with_keystone_can_edit_group_false(self):
|
||||
api.keystone.group_list(IgnoreArg()).AndReturn(self.groups.list())
|
||||
api.keystone.keystone_can_edit_group() \
|
||||
.MultipleTimes().AndReturn(False)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(GROUPS_INDEX_URL)
|
||||
|
||||
self.assertTemplateUsed(res, GROUPS_INDEX_VIEW_TEMPLATE)
|
||||
self.assertItemsEqual(res.context['table'].data, self.groups.list())
|
||||
|
||||
self.assertNotContains(res, 'Create Group')
|
||||
self.assertNotContains(res, 'Edit')
|
||||
self.assertNotContains(res, 'Delete Group')
|
||||
|
||||
@test.create_stubs({api.keystone: ('group_create', )})
|
||||
def test_create(self):
|
||||
group = self.groups.get(id="1")
|
||||
|
||||
api.keystone.group_create(IsA(http.HttpRequest),
|
||||
description=group.description,
|
||||
domain_id=None,
|
||||
name=group.name).AndReturn(group)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'method': 'CreateGroupForm',
|
||||
'name': group.name,
|
||||
'description': group.description}
|
||||
res = self.client.post(GROUP_CREATE_URL, formData)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertMessageCount(success=1)
|
||||
|
||||
@test.create_stubs({api.keystone: ('group_get',
|
||||
'group_update')})
|
||||
def test_update(self):
|
||||
group = self.groups.get(id="1")
|
||||
test_description = 'updated description'
|
||||
|
||||
api.keystone.group_get(IsA(http.HttpRequest), '1').AndReturn(group)
|
||||
api.keystone.group_update(IsA(http.HttpRequest),
|
||||
description=test_description,
|
||||
group_id=group.id,
|
||||
name=group.name).AndReturn(None)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'method': 'UpdateGroupForm',
|
||||
'group_id': group.id,
|
||||
'name': group.name,
|
||||
'description': test_description}
|
||||
|
||||
res = self.client.post(GROUP_UPDATE_URL, formData)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
|
||||
@test.create_stubs({api.keystone: ('group_list',
|
||||
'group_delete')})
|
||||
def test_delete_group(self):
|
||||
group = self.groups.get(id="2")
|
||||
|
||||
api.keystone.group_list(IgnoreArg()).AndReturn(self.groups.list())
|
||||
api.keystone.group_delete(IgnoreArg(), group.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'action': 'groups__delete__%s' % group.id}
|
||||
res = self.client.post(GROUPS_INDEX_URL, formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res, GROUPS_INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.keystone: ('group_get',
|
||||
'user_list',)})
|
||||
def test_manage(self):
|
||||
group = self.groups.get(id="1")
|
||||
group_members = self.users.list()
|
||||
|
||||
api.keystone.group_get(IsA(http.HttpRequest), group.id).\
|
||||
AndReturn(group)
|
||||
api.keystone.user_list(IgnoreArg(),
|
||||
group=group.id).\
|
||||
AndReturn(group_members)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(GROUP_MANAGE_URL)
|
||||
|
||||
self.assertTemplateUsed(res, GROUPS_MANAGE_VIEW_TEMPLATE)
|
||||
self.assertItemsEqual(res.context['table'].data, group_members)
|
||||
|
||||
@test.create_stubs({api.keystone: ('user_list',
|
||||
'remove_group_user')})
|
||||
def test_remove_user(self):
|
||||
group = self.groups.get(id="1")
|
||||
user = self.users.get(id="2")
|
||||
|
||||
api.keystone.user_list(IgnoreArg(),
|
||||
group=group.id).\
|
||||
AndReturn(self.users.list())
|
||||
api.keystone.remove_group_user(IgnoreArg(),
|
||||
group_id=group.id,
|
||||
user_id=user.id)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'action': 'group_members__removeGroupMember__%s' % user.id}
|
||||
res = self.client.post(GROUP_MANAGE_URL, formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res, GROUP_MANAGE_URL)
|
||||
self.assertMessageCount(success=1)
|
||||
|
||||
@test.create_stubs({api.keystone: ('group_get',
|
||||
'user_list',
|
||||
'add_group_user')})
|
||||
def test_add_user(self):
|
||||
group = self.groups.get(id="1")
|
||||
user = self.users.get(id="2")
|
||||
|
||||
api.keystone.group_get(IsA(http.HttpRequest), group.id).\
|
||||
AndReturn(group)
|
||||
api.keystone.user_list(IgnoreArg(),
|
||||
domain=group.domain_id).\
|
||||
AndReturn(self.users.list())
|
||||
api.keystone.user_list(IgnoreArg(),
|
||||
group=group.id).\
|
||||
AndReturn(self.users.list()[2:])
|
||||
|
||||
api.keystone.add_group_user(IgnoreArg(),
|
||||
group_id=group.id,
|
||||
user_id=user.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'action': 'group_non_members__addMember__%s' % user.id}
|
||||
res = self.client.post(GROUP_ADD_MEMBER_URL, formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res, GROUP_MANAGE_URL)
|
||||
self.assertMessageCount(success=1)
|
|
@ -0,0 +1,32 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 django.conf.urls.defaults import patterns, url
|
||||
|
||||
from .views import IndexView, CreateView, UpdateView, \
|
||||
ManageMembersView, NonMembersView
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', IndexView.as_view(), name='index'),
|
||||
url(r'^create$', CreateView.as_view(), name='create'),
|
||||
url(r'^(?P<group_id>[^/]+)/update/$',
|
||||
UpdateView.as_view(), name='update'),
|
||||
url(r'^(?P<group_id>[^/]+)/manage_members/$',
|
||||
ManageMembersView.as_view(), name='manage_members'),
|
||||
url(r'^(?P<group_id>[^/]+)/add_members/$',
|
||||
NonMembersView.as_view(), name='add_members'),
|
||||
)
|
|
@ -0,0 +1,146 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tables
|
||||
|
||||
from openstack_dashboard import api
|
||||
from .constants import GROUPS_INDEX_URL, GROUPS_INDEX_VIEW_TEMPLATE, \
|
||||
GROUPS_CREATE_VIEW_TEMPLATE, GROUPS_UPDATE_VIEW_TEMPLATE, \
|
||||
GROUPS_MANAGE_VIEW_TEMPLATE, GROUPS_ADD_MEMBER_VIEW_TEMPLATE, \
|
||||
GROUPS_ADD_MEMBER_AJAX_VIEW_TEMPLATE
|
||||
from .forms import CreateGroupForm, UpdateGroupForm
|
||||
from .tables import GroupsTable, GroupMembersTable, \
|
||||
GroupNonMembersTable
|
||||
|
||||
|
||||
class IndexView(tables.DataTableView):
|
||||
table_class = GroupsTable
|
||||
template_name = GROUPS_INDEX_VIEW_TEMPLATE
|
||||
|
||||
def get_data(self):
|
||||
groups = []
|
||||
try:
|
||||
groups = api.keystone.group_list(self.request)
|
||||
except:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve group list.'))
|
||||
return groups
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateGroupForm
|
||||
template_name = GROUPS_CREATE_VIEW_TEMPLATE
|
||||
success_url = reverse_lazy(GROUPS_INDEX_URL)
|
||||
|
||||
|
||||
class UpdateView(forms.ModalFormView):
|
||||
form_class = UpdateGroupForm
|
||||
template_name = GROUPS_UPDATE_VIEW_TEMPLATE
|
||||
success_url = reverse_lazy(GROUPS_INDEX_URL)
|
||||
|
||||
def get_object(self):
|
||||
if not hasattr(self, "_object"):
|
||||
try:
|
||||
self._object = api.keystone.group_get(self.request,
|
||||
self.kwargs['group_id'])
|
||||
except:
|
||||
redirect = reverse(GROUPS_INDEX_URL)
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to update group.'),
|
||||
redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
context['group'] = self.get_object()
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
group = self.get_object()
|
||||
return {'group_id': group.id,
|
||||
'name': group.name,
|
||||
'description': group.description}
|
||||
|
||||
|
||||
class GroupManageMixin(object):
|
||||
def _get_group(self):
|
||||
if not hasattr(self, "_group"):
|
||||
group_id = self.kwargs['group_id']
|
||||
self._group = api.keystone.group_get(self.request, group_id)
|
||||
return self._group
|
||||
|
||||
def _get_group_members(self):
|
||||
if not hasattr(self, "_group_members"):
|
||||
group_id = self.kwargs['group_id']
|
||||
self._group_members = api.keystone.user_list(self.request,
|
||||
group=group_id)
|
||||
return self._group_members
|
||||
|
||||
def _get_group_non_members(self):
|
||||
if not hasattr(self, "_group_non_members"):
|
||||
domain_id = self._get_group().domain_id
|
||||
all_users = api.keystone.user_list(self.request,
|
||||
domain=domain_id)
|
||||
group_members = self._get_group_members()
|
||||
group_member_ids = [user.id for user in group_members]
|
||||
self._group_non_members = filter(
|
||||
lambda u: u.id not in group_member_ids, all_users)
|
||||
return self._group_non_members
|
||||
|
||||
|
||||
class ManageMembersView(GroupManageMixin, tables.DataTableView):
|
||||
table_class = GroupMembersTable
|
||||
template_name = GROUPS_MANAGE_VIEW_TEMPLATE
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ManageMembersView, self).get_context_data(**kwargs)
|
||||
context['group'] = self._get_group()
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
group_members = []
|
||||
try:
|
||||
group_members = self._get_group_members()
|
||||
except:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve group users.'))
|
||||
return group_members
|
||||
|
||||
|
||||
class NonMembersView(GroupManageMixin, forms.ModalFormMixin,
|
||||
tables.DataTableView):
|
||||
template_name = GROUPS_ADD_MEMBER_VIEW_TEMPLATE
|
||||
ajax_template_name = GROUPS_ADD_MEMBER_AJAX_VIEW_TEMPLATE
|
||||
table_class = GroupNonMembersTable
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(NonMembersView, self).get_context_data(**kwargs)
|
||||
context['group'] = self._get_group()
|
||||
return context
|
||||
|
||||
def get_data(self):
|
||||
group_non_members = []
|
||||
try:
|
||||
group_non_members = self._get_group_non_members()
|
||||
except:
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve users.'))
|
||||
return group_non_members
|
|
@ -136,6 +136,7 @@ OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Member"
|
|||
OPENSTACK_KEYSTONE_BACKEND = {
|
||||
'name': 'native',
|
||||
'can_edit_user': True,
|
||||
'can_edit_group': True,
|
||||
'can_edit_project': True,
|
||||
'can_edit_domain': True,
|
||||
'can_edit_role': True
|
||||
|
|
|
@ -76,6 +76,7 @@ OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'test_domain'
|
|||
OPENSTACK_KEYSTONE_BACKEND = {
|
||||
'name': 'native',
|
||||
'can_edit_user': True,
|
||||
'can_edit_group': True,
|
||||
'can_edit_project': True,
|
||||
'can_edit_domain': True,
|
||||
'can_edit_role': True
|
||||
|
|
|
@ -18,7 +18,8 @@ from django.conf import settings
|
|||
from django.utils import datetime_safe
|
||||
|
||||
from keystoneclient.v2_0 import users, tenants, tokens, roles, ec2
|
||||
from keystoneclient.v3 import domains
|
||||
from keystoneclient.v3 import domains, groups
|
||||
|
||||
|
||||
from .utils import TestDataContainer
|
||||
|
||||
|
@ -99,6 +100,7 @@ def data(TEST):
|
|||
TEST.tokens = TestDataContainer()
|
||||
TEST.domains = TestDataContainer()
|
||||
TEST.users = TestDataContainer()
|
||||
TEST.groups = TestDataContainer()
|
||||
TEST.tenants = TestDataContainer()
|
||||
TEST.roles = TestDataContainer()
|
||||
TEST.ec2 = TestDataContainer()
|
||||
|
@ -154,6 +156,18 @@ def data(TEST):
|
|||
TEST.user = user # Your "current" user
|
||||
TEST.user.service_catalog = SERVICE_CATALOG
|
||||
|
||||
group_dict = {'id': "1",
|
||||
'name': 'group_one',
|
||||
'description': 'group one description',
|
||||
'domain_id': '1'}
|
||||
group = groups.Group(groups.GroupManager(None), group_dict)
|
||||
group_dict = {'id': "2",
|
||||
'name': 'group_two',
|
||||
'description': 'group two description',
|
||||
'domain_id': '1'}
|
||||
group2 = groups.Group(groups.GroupManager(None), group_dict)
|
||||
TEST.groups.add(group, group2)
|
||||
|
||||
tenant_dict = {'id': "1",
|
||||
'name': 'test_tenant',
|
||||
'description': "a test tenant.",
|
||||
|
|
Loading…
Reference in New Issue