Supports extra properties in project and user
This change is to support extra properties in project and user. To show an extra property in project/user table, you can customize. The explanation of the method to add an extra property is added by this patch. In addition, when you do a create or update user, you can specify a value of extra property by setting in local_settings.py Change-Id: Ifee491f2a55d9207fe5da70136f749e1fc4bab82 Implements: blueprint support-extra-prop-for-project-and-user
This commit is contained in:
parent
0e0b01fd27
commit
ab11eef92a
@ -402,6 +402,8 @@ files to be added. There are some example files available within this folder, w
|
|||||||
customization as much as possible, and support for this is given preference over more
|
customization as much as possible, and support for this is given preference over more
|
||||||
exotic methods such as monkey patching and overrides files.
|
exotic methods such as monkey patching and overrides files.
|
||||||
|
|
||||||
|
.. _horizon-customization-module:
|
||||||
|
|
||||||
Horizon customization module (overrides)
|
Horizon customization module (overrides)
|
||||||
========================================
|
========================================
|
||||||
|
|
||||||
@ -515,6 +517,35 @@ similar way, add the new column definition and then use the ``Meta``
|
|||||||
WSGIDaemonProcess [... existing options ...] python-path=/opt/python
|
WSGIDaemonProcess [... existing options ...] python-path=/opt/python
|
||||||
|
|
||||||
|
|
||||||
|
Customize the project and user table columns
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
|
||||||
|
Keystone V3 has a place to store extra information regarding project and user.
|
||||||
|
Using the override mechanism described in :ref:`horizon-customization-module`,
|
||||||
|
Horizon is able to show these extra information as a custom column.
|
||||||
|
For example, if a user in Keystone has an attribute ``phone_num``, you could
|
||||||
|
define new column::
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import forms
|
||||||
|
from horizon import tables
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.identity.users import tables as user_tables
|
||||||
|
from openstack_dashboard.dashboards.identity.users import views
|
||||||
|
|
||||||
|
class MyUsersTable(user_tables.UsersTable):
|
||||||
|
phone_num = tables.Column('phone_num',
|
||||||
|
verbose_name=_('Phone Number'),
|
||||||
|
form_field=forms.CharField(),)
|
||||||
|
|
||||||
|
class Meta(user_tables.UsersTable.Meta):
|
||||||
|
columns = ('name', 'description', 'phone_num')
|
||||||
|
|
||||||
|
views.IndexView.table_class = MyUsersTable
|
||||||
|
|
||||||
|
|
||||||
Icons
|
Icons
|
||||||
=====
|
=====
|
||||||
|
|
||||||
|
@ -1700,6 +1700,34 @@ This setting controls the behavior of the operation log.
|
|||||||
* %(param)s
|
* %(param)s
|
||||||
|
|
||||||
|
|
||||||
|
``PROJECT_TABLE_EXTRA_INFO``
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
.. versionadded:: 10.0.0(Newton)
|
||||||
|
|
||||||
|
Default: ``{}``
|
||||||
|
|
||||||
|
Add additional information for project as an extra attribute.
|
||||||
|
Project and user can have any attributes by keystone mechanism.
|
||||||
|
This setting can treat these attributes on Horizon when only
|
||||||
|
using Keystone v3.
|
||||||
|
For example::
|
||||||
|
|
||||||
|
PROJECT_TABLE_EXTRA_INFO = {
|
||||||
|
'phone_num': _('Phone Number'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
``USER_TABLE_EXTRA_INFO``
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. versionadded:: 10.0.0(Newton)
|
||||||
|
|
||||||
|
Default: ``{}``
|
||||||
|
|
||||||
|
Same as ``PROJECT_TABLE_EXTRA_INFO``, add additional information for user.
|
||||||
|
|
||||||
|
|
||||||
Django Settings (Partial)
|
Django Settings (Partial)
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
@ -375,7 +375,7 @@ def user_list(request, project=None, domain=None, group=None, filters=None):
|
|||||||
|
|
||||||
|
|
||||||
def user_create(request, name=None, email=None, password=None, project=None,
|
def user_create(request, name=None, email=None, password=None, project=None,
|
||||||
enabled=None, domain=None, description=None):
|
enabled=None, domain=None, description=None, **data):
|
||||||
manager = keystoneclient(request, admin=True).users
|
manager = keystoneclient(request, admin=True).users
|
||||||
try:
|
try:
|
||||||
if VERSIONS.active < 3:
|
if VERSIONS.active < 3:
|
||||||
@ -384,7 +384,8 @@ def user_create(request, name=None, email=None, password=None, project=None,
|
|||||||
else:
|
else:
|
||||||
return manager.create(name, password=password, email=email,
|
return manager.create(name, password=password, email=email,
|
||||||
default_project=project, enabled=enabled,
|
default_project=project, enabled=enabled,
|
||||||
domain=domain, description=description)
|
domain=domain, description=description,
|
||||||
|
**data)
|
||||||
except keystone_exceptions.Conflict:
|
except keystone_exceptions.Conflict:
|
||||||
raise exceptions.Conflict()
|
raise exceptions.Conflict()
|
||||||
|
|
||||||
|
@ -10,6 +10,12 @@
|
|||||||
<dd>{{ project.enabled|yesno|capfirst }}</dd>
|
<dd>{{ project.enabled|yesno|capfirst }}</dd>
|
||||||
<dt>{% trans "Description" %}</dt>
|
<dt>{% trans "Description" %}</dt>
|
||||||
<dd>{{ project.description|default:_("None") }}</dd>
|
<dd>{{ project.description|default:_("None") }}</dd>
|
||||||
|
{% if extras %}
|
||||||
|
{% for key, value in extras.items %}
|
||||||
|
<dt>{{ key }}</dt>
|
||||||
|
<dd>{{ value }}</dd>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -299,6 +299,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
|||||||
self.assertEqual(step.action.initial['subnet'],
|
self.assertEqual(step.action.initial['subnet'],
|
||||||
neutron_quotas.get('subnet').limit)
|
neutron_quotas.get('subnet').limit)
|
||||||
|
|
||||||
|
@override_settings(PROJECT_TABLE_EXTRA_INFO={'phone_num': 'Phone Number'})
|
||||||
@test.create_stubs({api.keystone: ('get_default_role',
|
@test.create_stubs({api.keystone: ('get_default_role',
|
||||||
'add_tenant_user_role',
|
'add_tenant_user_role',
|
||||||
'tenant_create',
|
'tenant_create',
|
||||||
@ -321,6 +322,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
|||||||
users = self._get_all_users(domain_id)
|
users = self._get_all_users(domain_id)
|
||||||
groups = self._get_all_groups(domain_id)
|
groups = self._get_all_groups(domain_id)
|
||||||
roles = self.roles.list()
|
roles = self.roles.list()
|
||||||
|
# extra info
|
||||||
|
phone_number = "+81-3-1234-5678"
|
||||||
|
|
||||||
# init
|
# init
|
||||||
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
|
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
|
||||||
@ -338,6 +341,8 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
|||||||
|
|
||||||
# handle
|
# handle
|
||||||
project_details = self._get_project_info(project)
|
project_details = self._get_project_info(project)
|
||||||
|
# add extra info
|
||||||
|
project_details.update({'phone_num': phone_number})
|
||||||
quota_data = self._get_quota_info(quota)
|
quota_data = self._get_quota_info(quota)
|
||||||
|
|
||||||
api.keystone.tenant_create(IsA(http.HttpRequest), **project_details) \
|
api.keystone.tenant_create(IsA(http.HttpRequest), **project_details) \
|
||||||
@ -377,6 +382,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
|
|||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
workflow_data.update(self._get_workflow_data(project, quota))
|
workflow_data.update(self._get_workflow_data(project, quota))
|
||||||
|
workflow_data.update({'phone_num': phone_number})
|
||||||
|
|
||||||
url = reverse('horizon:identity:projects:create')
|
url = reverse('horizon:identity:projects:create')
|
||||||
res = self.client.post(url, workflow_data)
|
res = self.client.post(url, workflow_data)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
@ -198,8 +199,13 @@ class UpdateProjectView(workflows.WorkflowView):
|
|||||||
for field in PROJECT_INFO_FIELDS:
|
for field in PROJECT_INFO_FIELDS:
|
||||||
initial[field] = getattr(project_info, field, None)
|
initial[field] = getattr(project_info, field, None)
|
||||||
|
|
||||||
# Retrieve the domain name where the project belong
|
|
||||||
if keystone.VERSIONS.active >= 3:
|
if keystone.VERSIONS.active >= 3:
|
||||||
|
# get extra columns info
|
||||||
|
ex_info = getattr(settings, 'PROJECT_TABLE_EXTRA_INFO', {})
|
||||||
|
for ex_field in ex_info:
|
||||||
|
initial[ex_field] = getattr(project_info, ex_field, None)
|
||||||
|
|
||||||
|
# Retrieve the domain name where the project belong
|
||||||
try:
|
try:
|
||||||
if policy.check((("identity", "identity:get_domain"),),
|
if policy.check((("identity", "identity:get_domain"),),
|
||||||
self.request):
|
self.request):
|
||||||
@ -245,6 +251,12 @@ class DetailProjectView(views.HorizonTemplateView):
|
|||||||
context["project"] = project
|
context["project"] = project
|
||||||
context["url"] = reverse(INDEX_URL)
|
context["url"] = reverse(INDEX_URL)
|
||||||
context["actions"] = table.render_row_actions(project)
|
context["actions"] = table.render_row_actions(project)
|
||||||
|
|
||||||
|
if keystone.VERSIONS.active >= 3:
|
||||||
|
extra_info = getattr(settings, 'PROJECT_TABLE_EXTRA_INFO', {})
|
||||||
|
context['extras'] = dict(
|
||||||
|
(display_key, getattr(project, key, ''))
|
||||||
|
for key, display_key in extra_info.items())
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@memoized.memoized_method
|
@memoized.memoized_method
|
||||||
|
@ -169,6 +169,14 @@ class CreateProjectInfoAction(workflows.Action):
|
|||||||
readonlyInput = forms.TextInput(attrs={'readonly': 'readonly'})
|
readonlyInput = forms.TextInput(attrs={'readonly': 'readonly'})
|
||||||
self.fields["domain_id"].widget = readonlyInput
|
self.fields["domain_id"].widget = readonlyInput
|
||||||
self.fields["domain_name"].widget = readonlyInput
|
self.fields["domain_name"].widget = readonlyInput
|
||||||
|
self.add_extra_fields()
|
||||||
|
|
||||||
|
def add_extra_fields(self):
|
||||||
|
# add extra column defined by setting
|
||||||
|
EXTRA_INFO = getattr(settings, 'PROJECT_TABLE_EXTRA_INFO', {})
|
||||||
|
for key, value in EXTRA_INFO.items():
|
||||||
|
form = forms.CharField(label=value, required=False,)
|
||||||
|
self.fields[key] = form
|
||||||
|
|
||||||
class Meta(object):
|
class Meta(object):
|
||||||
name = _("Project Information")
|
name = _("Project Information")
|
||||||
@ -185,6 +193,12 @@ class CreateProjectInfo(workflows.Step):
|
|||||||
"description",
|
"description",
|
||||||
"enabled")
|
"enabled")
|
||||||
|
|
||||||
|
def __init__(self, workflow):
|
||||||
|
super(CreateProjectInfo, self).__init__(workflow)
|
||||||
|
if keystone.VERSIONS.active >= 3:
|
||||||
|
EXTRA_INFO = getattr(settings, 'PROJECT_TABLE_EXTRA_INFO', {})
|
||||||
|
self.contributes += tuple(EXTRA_INFO.keys())
|
||||||
|
|
||||||
|
|
||||||
class UpdateProjectMembersAction(workflows.MembershipAction):
|
class UpdateProjectMembersAction(workflows.MembershipAction):
|
||||||
def __init__(self, request, *args, **kwargs):
|
def __init__(self, request, *args, **kwargs):
|
||||||
@ -445,12 +459,20 @@ class CreateProject(CommonQuotaWorkflow):
|
|||||||
# create the project
|
# create the project
|
||||||
domain_id = data['domain_id']
|
domain_id = data['domain_id']
|
||||||
try:
|
try:
|
||||||
|
# add extra information
|
||||||
|
if keystone.VERSIONS.active >= 3:
|
||||||
|
EXTRA_INFO = getattr(settings, 'PROJECT_TABLE_EXTRA_INFO', {})
|
||||||
|
kwargs = dict((key, data.get(key)) for key in EXTRA_INFO)
|
||||||
|
else:
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
desc = data['description']
|
desc = data['description']
|
||||||
self.object = api.keystone.tenant_create(request,
|
self.object = api.keystone.tenant_create(request,
|
||||||
name=data['name'],
|
name=data['name'],
|
||||||
description=desc,
|
description=desc,
|
||||||
enabled=data['enabled'],
|
enabled=data['enabled'],
|
||||||
domain=domain_id)
|
domain=domain_id,
|
||||||
|
**kwargs)
|
||||||
return self.object
|
return self.object
|
||||||
except exceptions.Conflict:
|
except exceptions.Conflict:
|
||||||
msg = _('Project name "%s" is already used.') % data['name']
|
msg = _('Project name "%s" is already used.') % data['name']
|
||||||
@ -608,6 +630,12 @@ class UpdateProjectInfo(workflows.Step):
|
|||||||
"description",
|
"description",
|
||||||
"enabled")
|
"enabled")
|
||||||
|
|
||||||
|
def __init__(self, workflow):
|
||||||
|
super(UpdateProjectInfo, self).__init__(workflow)
|
||||||
|
if keystone.VERSIONS.active >= 3:
|
||||||
|
EXTRA_INFO = getattr(settings, 'PROJECT_TABLE_EXTRA_INFO', {})
|
||||||
|
self.contributes += tuple(EXTRA_INFO.keys())
|
||||||
|
|
||||||
|
|
||||||
class UpdateProject(CommonQuotaWorkflow):
|
class UpdateProject(CommonQuotaWorkflow):
|
||||||
slug = "update_project"
|
slug = "update_project"
|
||||||
@ -649,13 +677,22 @@ class UpdateProject(CommonQuotaWorkflow):
|
|||||||
domain_id = api.keystone.get_effective_domain_id(self.request)
|
domain_id = api.keystone.get_effective_domain_id(self.request)
|
||||||
try:
|
try:
|
||||||
project_id = data['project_id']
|
project_id = data['project_id']
|
||||||
|
|
||||||
|
# add extra information
|
||||||
|
if keystone.VERSIONS.active >= 3:
|
||||||
|
EXTRA_INFO = getattr(settings, 'PROJECT_TABLE_EXTRA_INFO', {})
|
||||||
|
kwargs = dict((key, data.get(key)) for key in EXTRA_INFO)
|
||||||
|
else:
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
return api.keystone.tenant_update(
|
return api.keystone.tenant_update(
|
||||||
request,
|
request,
|
||||||
project_id,
|
project_id,
|
||||||
name=data['name'],
|
name=data['name'],
|
||||||
description=data['description'],
|
description=data['description'],
|
||||||
enabled=data['enabled'],
|
enabled=data['enabled'],
|
||||||
domain=domain_id)
|
domain=domain_id,
|
||||||
|
**kwargs)
|
||||||
except exceptions.Conflict:
|
except exceptions.Conflict:
|
||||||
msg = _('Project name "%s" is already used.') % data['name']
|
msg = _('Project name "%s" is already used.') % data['name']
|
||||||
self.failure_message = msg
|
self.failure_message = msg
|
||||||
|
@ -93,10 +93,22 @@ class BaseUserForm(forms.SelfHandlingForm):
|
|||||||
LOG.debug("User: %s has no projects" % user_id)
|
LOG.debug("User: %s has no projects" % user_id)
|
||||||
|
|
||||||
|
|
||||||
|
class AddExtraColumnMixIn(object):
|
||||||
|
def add_extra_fields(self, ordering=None):
|
||||||
|
if api.keystone.VERSIONS.active >= 3:
|
||||||
|
# add extra column defined by setting
|
||||||
|
EXTRA_INFO = getattr(settings, 'USER_TABLE_EXTRA_INFO', {})
|
||||||
|
for key, value in EXTRA_INFO.items():
|
||||||
|
self.fields[key] = forms.CharField(label=value,
|
||||||
|
required=False)
|
||||||
|
if ordering:
|
||||||
|
ordering.append(key)
|
||||||
|
|
||||||
|
|
||||||
ADD_PROJECT_URL = "horizon:identity:projects:create"
|
ADD_PROJECT_URL = "horizon:identity:projects:create"
|
||||||
|
|
||||||
|
|
||||||
class CreateUserForm(PasswordMixin, BaseUserForm):
|
class CreateUserForm(PasswordMixin, BaseUserForm, AddExtraColumnMixIn):
|
||||||
# Hide the domain_id and domain_name by default
|
# Hide the domain_id and domain_name by default
|
||||||
domain_id = forms.CharField(label=_("Domain ID"),
|
domain_id = forms.CharField(label=_("Domain ID"),
|
||||||
required=False,
|
required=False,
|
||||||
@ -129,6 +141,7 @@ class CreateUserForm(PasswordMixin, BaseUserForm):
|
|||||||
"description", "email", "password",
|
"description", "email", "password",
|
||||||
"confirm_password", "project", "role_id",
|
"confirm_password", "project", "role_id",
|
||||||
"enabled"]
|
"enabled"]
|
||||||
|
self.add_extra_fields(ordering)
|
||||||
self.fields = collections.OrderedDict(
|
self.fields = collections.OrderedDict(
|
||||||
(key, self.fields[key]) for key in ordering)
|
(key, self.fields[key]) for key in ordering)
|
||||||
role_choices = [(role.id, role.name) for role in roles]
|
role_choices = [(role.id, role.name) for role in roles]
|
||||||
@ -153,6 +166,14 @@ class CreateUserForm(PasswordMixin, BaseUserForm):
|
|||||||
desc = data["description"]
|
desc = data["description"]
|
||||||
if "email" in data:
|
if "email" in data:
|
||||||
data['email'] = data['email'] or None
|
data['email'] = data['email'] or None
|
||||||
|
|
||||||
|
# add extra information
|
||||||
|
if api.keystone.VERSIONS.active >= 3:
|
||||||
|
EXTRA_INFO = getattr(settings, 'USER_TABLE_EXTRA_INFO', {})
|
||||||
|
kwargs = dict((key, data.get(key)) for key in EXTRA_INFO)
|
||||||
|
else:
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
new_user = \
|
new_user = \
|
||||||
api.keystone.user_create(request,
|
api.keystone.user_create(request,
|
||||||
name=data['name'],
|
name=data['name'],
|
||||||
@ -161,7 +182,8 @@ class CreateUserForm(PasswordMixin, BaseUserForm):
|
|||||||
password=data['password'],
|
password=data['password'],
|
||||||
project=data['project'] or None,
|
project=data['project'] or None,
|
||||||
enabled=data['enabled'],
|
enabled=data['enabled'],
|
||||||
domain=domain.id)
|
domain=domain.id,
|
||||||
|
**kwargs)
|
||||||
messages.success(request,
|
messages.success(request,
|
||||||
_('User "%s" was successfully created.')
|
_('User "%s" was successfully created.')
|
||||||
% data['name'])
|
% data['name'])
|
||||||
@ -189,7 +211,7 @@ class CreateUserForm(PasswordMixin, BaseUserForm):
|
|||||||
exceptions.handle(request, _('Unable to create user.'))
|
exceptions.handle(request, _('Unable to create user.'))
|
||||||
|
|
||||||
|
|
||||||
class UpdateUserForm(BaseUserForm):
|
class UpdateUserForm(BaseUserForm, AddExtraColumnMixIn):
|
||||||
# Hide the domain_id and domain_name by default
|
# Hide the domain_id and domain_name by default
|
||||||
domain_id = forms.CharField(label=_("Domain ID"),
|
domain_id = forms.CharField(label=_("Domain ID"),
|
||||||
required=False,
|
required=False,
|
||||||
@ -211,7 +233,7 @@ class UpdateUserForm(BaseUserForm):
|
|||||||
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
def __init__(self, request, *args, **kwargs):
|
||||||
super(UpdateUserForm, self).__init__(request, *args, **kwargs)
|
super(UpdateUserForm, self).__init__(request, *args, **kwargs)
|
||||||
|
self.add_extra_fields()
|
||||||
if api.keystone.keystone_can_edit_user() is False:
|
if api.keystone.keystone_can_edit_user() is False:
|
||||||
for field in ('name', 'email'):
|
for field in ('name', 'email'):
|
||||||
self.fields.pop(field)
|
self.fields.pop(field)
|
||||||
|
@ -28,6 +28,12 @@
|
|||||||
<dt>{% trans "Primary Project Name" %}</dt>
|
<dt>{% trans "Primary Project Name" %}</dt>
|
||||||
<dd>{{ tenant_name }}</dd>
|
<dd>{{ tenant_name }}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if extras %}
|
||||||
|
{% for key, value in extras.items %}
|
||||||
|
<dt>{{ key }}</dt>
|
||||||
|
<dd>{{ value }}</dd>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -84,6 +84,7 @@ class UsersViewTests(test.BaseAdminViewTests):
|
|||||||
domain_context_name=domain.name)
|
domain_context_name=domain.name)
|
||||||
self.test_index()
|
self.test_index()
|
||||||
|
|
||||||
|
@override_settings(USER_TABLE_EXTRA_INFO={'phone_num': 'Phone Number'})
|
||||||
@test.create_stubs({api.keystone: ('user_create',
|
@test.create_stubs({api.keystone: ('user_create',
|
||||||
'get_default_domain',
|
'get_default_domain',
|
||||||
'tenant_list',
|
'tenant_list',
|
||||||
@ -95,6 +96,7 @@ class UsersViewTests(test.BaseAdminViewTests):
|
|||||||
user = self.users.get(id="1")
|
user = self.users.get(id="1")
|
||||||
domain = self._get_default_domain()
|
domain = self._get_default_domain()
|
||||||
domain_id = domain.id
|
domain_id = domain.id
|
||||||
|
phone_number = "+81-3-1234-5678"
|
||||||
|
|
||||||
role = self.roles.first()
|
role = self.roles.first()
|
||||||
|
|
||||||
@ -112,6 +114,7 @@ class UsersViewTests(test.BaseAdminViewTests):
|
|||||||
IgnoreArg(), user=None).AndReturn(
|
IgnoreArg(), user=None).AndReturn(
|
||||||
[self.tenants.list(), False])
|
[self.tenants.list(), False])
|
||||||
|
|
||||||
|
kwargs = {'phone_num': phone_number}
|
||||||
api.keystone.user_create(IgnoreArg(),
|
api.keystone.user_create(IgnoreArg(),
|
||||||
name=user.name,
|
name=user.name,
|
||||||
description=user.description,
|
description=user.description,
|
||||||
@ -119,7 +122,8 @@ class UsersViewTests(test.BaseAdminViewTests):
|
|||||||
password=user.password,
|
password=user.password,
|
||||||
project=self.tenant.id,
|
project=self.tenant.id,
|
||||||
enabled=True,
|
enabled=True,
|
||||||
domain=domain_id).AndReturn(user)
|
domain=domain_id,
|
||||||
|
**kwargs).AndReturn(user)
|
||||||
api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
|
api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
|
||||||
api.keystone.get_default_role(IgnoreArg()).AndReturn(role)
|
api.keystone.get_default_role(IgnoreArg()).AndReturn(role)
|
||||||
api.keystone.roles_for_user(IgnoreArg(), user.id, self.tenant.id)
|
api.keystone.roles_for_user(IgnoreArg(), user.id, self.tenant.id)
|
||||||
@ -137,7 +141,8 @@ class UsersViewTests(test.BaseAdminViewTests):
|
|||||||
'project': self.tenant.id,
|
'project': self.tenant.id,
|
||||||
'role_id': self.roles.first().id,
|
'role_id': self.roles.first().id,
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'confirm_password': user.password}
|
'confirm_password': user.password,
|
||||||
|
'phone_num': phone_number}
|
||||||
res = self.client.post(USER_CREATE_URL, formData)
|
res = self.client.post(USER_CREATE_URL, formData)
|
||||||
|
|
||||||
self.assertNoFormErrors(res)
|
self.assertNoFormErrors(res)
|
||||||
@ -370,6 +375,7 @@ class UsersViewTests(test.BaseAdminViewTests):
|
|||||||
res, "form", 'password',
|
res, "form", 'password',
|
||||||
['Password must be between 8 and 18 characters.'])
|
['Password must be between 8 and 18 characters.'])
|
||||||
|
|
||||||
|
@override_settings(USER_TABLE_EXTRA_INFO={'phone_num': 'Phone Number'})
|
||||||
@test.create_stubs({api.keystone: ('user_get',
|
@test.create_stubs({api.keystone: ('user_get',
|
||||||
'domain_get',
|
'domain_get',
|
||||||
'tenant_list',
|
'tenant_list',
|
||||||
@ -381,6 +387,7 @@ class UsersViewTests(test.BaseAdminViewTests):
|
|||||||
user = self.users.get(id="1")
|
user = self.users.get(id="1")
|
||||||
domain_id = user.domain_id
|
domain_id = user.domain_id
|
||||||
domain = self.domains.get(id=domain_id)
|
domain = self.domains.get(id=domain_id)
|
||||||
|
phone_number = "+81-3-1234-5678"
|
||||||
|
|
||||||
api.keystone.user_get(IsA(http.HttpRequest), '1',
|
api.keystone.user_get(IsA(http.HttpRequest), '1',
|
||||||
admin=True).AndReturn(user)
|
admin=True).AndReturn(user)
|
||||||
@ -396,10 +403,12 @@ class UsersViewTests(test.BaseAdminViewTests):
|
|||||||
IgnoreArg(), user=user.id).AndReturn(
|
IgnoreArg(), user=user.id).AndReturn(
|
||||||
[self.tenants.list(), False])
|
[self.tenants.list(), False])
|
||||||
|
|
||||||
|
kwargs = {'phone_num': phone_number}
|
||||||
api.keystone.user_update(IsA(http.HttpRequest),
|
api.keystone.user_update(IsA(http.HttpRequest),
|
||||||
user.id,
|
user.id,
|
||||||
email=user.email,
|
email=user.email,
|
||||||
name=user.name).AndReturn(None)
|
name=user.name,
|
||||||
|
**kwargs).AndReturn(None)
|
||||||
|
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
@ -408,8 +417,8 @@ class UsersViewTests(test.BaseAdminViewTests):
|
|||||||
'name': user.name,
|
'name': user.name,
|
||||||
'description': user.description,
|
'description': user.description,
|
||||||
'email': user.email,
|
'email': user.email,
|
||||||
'project': self.tenant.id}
|
'project': self.tenant.id,
|
||||||
|
'phone_num': phone_number}
|
||||||
res = self.client.post(USER_UPDATE_URL, formData)
|
res = self.client.post(USER_UPDATE_URL, formData)
|
||||||
|
|
||||||
self.assertNoFormErrors(res)
|
self.assertNoFormErrors(res)
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
from django.utils.decorators import method_decorator # noqa
|
from django.utils.decorators import method_decorator # noqa
|
||||||
@ -129,13 +130,18 @@ class UpdateView(forms.ModalFormView):
|
|||||||
except Exception:
|
except Exception:
|
||||||
exceptions.handle(self.request,
|
exceptions.handle(self.request,
|
||||||
_('Unable to retrieve project domain.'))
|
_('Unable to retrieve project domain.'))
|
||||||
return {'domain_id': domain_id,
|
|
||||||
|
data = {'domain_id': domain_id,
|
||||||
'domain_name': domain_name,
|
'domain_name': domain_name,
|
||||||
'id': user.id,
|
'id': user.id,
|
||||||
'name': user.name,
|
'name': user.name,
|
||||||
'project': user.project_id,
|
'project': user.project_id,
|
||||||
'email': getattr(user, 'email', None),
|
'email': getattr(user, 'email', None),
|
||||||
'description': getattr(user, 'description', None)}
|
'description': getattr(user, 'description', None)}
|
||||||
|
if api.keystone.VERSIONS.active >= 3:
|
||||||
|
for key in getattr(settings, 'USER_TABLE_EXTRA_INFO', {}):
|
||||||
|
data[key] = getattr(user, key, None)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class CreateView(forms.ModalFormView):
|
class CreateView(forms.ModalFormView):
|
||||||
@ -200,7 +206,10 @@ class DetailView(views.HorizonTemplateView):
|
|||||||
exceptions.handle(self.request,
|
exceptions.handle(self.request,
|
||||||
_('Unable to retrieve project domain.'))
|
_('Unable to retrieve project domain.'))
|
||||||
context["description"] = getattr(user, "description", _("None"))
|
context["description"] = getattr(user, "description", _("None"))
|
||||||
|
extra_info = getattr(settings, 'USER_TABLE_EXTRA_INFO', {})
|
||||||
|
context['extras'] = dict(
|
||||||
|
(display_key, getattr(user, key, ''))
|
||||||
|
for key, display_key in extra_info.items())
|
||||||
context["user"] = user
|
context["user"] = user
|
||||||
if tenant:
|
if tenant:
|
||||||
context["tenant_name"] = tenant.name
|
context["tenant_name"] = tenant.name
|
||||||
|
@ -796,3 +796,19 @@ REST_API_REQUIRED_SETTINGS = ['OPENSTACK_HYPERVISOR_FEATURES',
|
|||||||
# 'ipv6': ['fc00::/7']
|
# 'ipv6': ['fc00::/7']
|
||||||
#}
|
#}
|
||||||
ALLOWED_PRIVATE_SUBNET_CIDR = {'ipv4': [], 'ipv6': []}
|
ALLOWED_PRIVATE_SUBNET_CIDR = {'ipv4': [], 'ipv6': []}
|
||||||
|
|
||||||
|
# Project and user can have any attributes by keystone v3 mechanism.
|
||||||
|
# This settings can treat these attributes on Horizon.
|
||||||
|
# It means, when you show Create/Update modal, attribute below is
|
||||||
|
# shown and you can specify any value.
|
||||||
|
# If you'd like to display these extra data in project or user index table,
|
||||||
|
# Keystone v3 allows you to add extra properties to Project and Users.
|
||||||
|
# Horizon's customization (http://docs.openstack.org/developer/horizon/topics/customizing.html#horizon-customization-module-overrides)
|
||||||
|
# allows you to display this extra information in the Create/Update modal and
|
||||||
|
# the corresponding tables.
|
||||||
|
#PROJECT_TABLE_EXTRA_INFO = {
|
||||||
|
# 'phone_num': _('Phone Number'),
|
||||||
|
#}
|
||||||
|
#USER_TABLE_EXTRA_INFO = {
|
||||||
|
# 'phone_num': _('Phone Number'),
|
||||||
|
#}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- >
|
||||||
|
[`blueprint Supports extra properties in project and user <https://blueprints.launchpad.net/horizon/+spec/support-extra-prop-for-project-and-user>`_]
|
||||||
|
Support an ability to treat additional information for
|
||||||
|
project and user as an extra attribute.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user