diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py index f3e9aa6f69..aeacc95f99 100644 --- a/openstack_dashboard/api/cinder.py +++ b/openstack_dashboard/api/cinder.py @@ -1139,9 +1139,13 @@ def group_type_create(request, name, description=None, is_public=None): @profiler.trace -def group_type_update(request, group_type_id, data): +def group_type_update(request, group_type_id, name=None, description=None, + is_public=None): client = _cinderclient_with_generic_groups(request) - return GroupType(client.group_types.update(group_type_id, **data)) + return GroupType(client.group_types.update(group_type_id, + name, + description, + is_public)) @profiler.trace diff --git a/openstack_dashboard/dashboards/admin/group_types/__init__.py b/openstack_dashboard/dashboards/admin/group_types/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/admin/group_types/forms.py b/openstack_dashboard/dashboards/admin/group_types/forms.py new file mode 100644 index 0000000000..6d57604181 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/forms.py @@ -0,0 +1,107 @@ +# 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.forms import ValidationError +from django.urls import reverse +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages + +from openstack_dashboard.api import cinder + + +class CreateGroupType(forms.SelfHandlingForm): + name = forms.CharField(max_length=255, label=_("Name")) + group_type_description = forms.CharField( + max_length=255, + widget=forms.Textarea(attrs={'rows': 4}), + label=_("Description"), + required=False) + is_public = forms.BooleanField( + label=_("Public"), + initial=True, + required=False, + help_text=_("By default, group type is created as public. To " + "create a private group type, uncheck this field.")) + + def clean_name(self): + cleaned_name = self.cleaned_data['name'] + if cleaned_name.isspace(): + raise ValidationError(_('Group type name can not be empty.')) + + return cleaned_name + + def handle(self, request, data): + try: + group_type = cinder.group_type_create( + request, + data['name'], + data['group_type_description'], + data['is_public']) + messages.success(request, _('Successfully created group type: %s') + % data['name']) + return group_type + except Exception as e: + if getattr(e, 'code', None) == 409: + msg = _('Group type name "%s" already ' + 'exists.') % data['name'] + self._errors['name'] = self.error_class([msg]) + else: + redirect = reverse("horizon:admin:group_types:index") + exceptions.handle(request, + _('Unable to create group type.'), + redirect=redirect) + + +class EditGroupType(forms.SelfHandlingForm): + name = forms.CharField(max_length=255, + label=_("Name")) + description = forms.CharField(max_length=255, + widget=forms.Textarea(attrs={'rows': 4}), + label=_("Description"), + required=False) + is_public = forms.BooleanField(label=_("Public"), required=False, + help_text=_( + "To make group type private, uncheck " + "this field.")) + + def clean_name(self): + cleaned_name = self.cleaned_data['name'] + if cleaned_name.isspace(): + msg = _('New name cannot be empty.') + self._errors['name'] = self.error_class([msg]) + + return cleaned_name + + def handle(self, request, data): + group_type_id = self.initial['id'] + try: + cinder.group_type_update(request, + group_type_id, + data['name'], + data['description'], + data['is_public']) + message = _('Successfully updated group type.') + messages.success(request, message) + return True + except Exception as ex: + redirect = reverse("horizon:admin:group_types:index") + if ex.code == 409: + error_message = _('New name conflicts with another ' + 'group type.') + else: + error_message = _('Unable to update group type.') + + exceptions.handle(request, error_message, + redirect=redirect) diff --git a/openstack_dashboard/dashboards/admin/group_types/panel.py b/openstack_dashboard/dashboards/admin/group_types/panel.py new file mode 100644 index 0000000000..392fd25f18 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/panel.py @@ -0,0 +1,24 @@ +# 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 + + +class GroupTypes(horizon.Panel): + name = _("Group Types") + slug = 'group_types' + permissions = ( + ('openstack.services.volume', 'openstack.services.volumev3'), + ) + policy_rules = (("volume", "group:group_types_manage"),) diff --git a/openstack_dashboard/dashboards/admin/group_types/tables.py b/openstack_dashboard/dashboards/admin/group_types/tables.py new file mode 100644 index 0000000000..29eab0c2f9 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/tables.py @@ -0,0 +1,124 @@ +# 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.template import defaultfilters as filters +from django.urls import reverse +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy + +from horizon import exceptions +from horizon import forms +from horizon import tables + +from openstack_dashboard.api import cinder + + +class CreateGroupType(tables.LinkAction): + name = "create" + verbose_name = _("Create Group Type") + url = "horizon:admin:group_types:create_type" + classes = ("ajax-modal",) + icon = "plus" + policy_rules = (("volume", "group:group_types_manage"),) + + +class EditGroupType(tables.LinkAction): + name = "edit" + verbose_name = _("Edit Group Type") + url = "horizon:admin:group_types:update_type" + classes = ("ajax-modal",) + icon = "pencil" + policy_rules = (("volume", "group:group_types_manage"),) + + +class GroupTypesFilterAction(tables.FilterAction): + + def filter(self, table, group_types, filter_string): + """Naive case-insensitive search.""" + query = filter_string.lower() + return [group_type for group_type in group_types + if query in group_type.name.lower()] + + +class DeleteGroupType(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Group Type", + u"Delete Group Types", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Group Type", + u"Deleted Group Types", + count + ) + policy_rules = (("volume", "group:group_types_manage"),) + + def delete(self, request, obj_id): + try: + cinder.group_type_delete(request, obj_id) + except exceptions.BadRequest as e: + redirect_url = reverse("horizon:admin:group_types:index") + exceptions.handle(request, e, redirect=redirect_url) + + +class UpdateRow(tables.Row): + ajax = True + + def get_data(self, request, group_type_id): + try: + group_type = \ + cinder.group_type_get(request, group_type_id) + except Exception: + exceptions.handle(request, + _('Unable to retrieve group type.')) + return group_type + + +class GroupTypesTable(tables.DataTable): + name = tables.WrappingColumn("name", verbose_name=_("Name"), + form_field=forms.CharField(max_length=64)) + description = tables.Column(lambda obj: getattr(obj, 'description', None), + verbose_name=_('Description'), + form_field=forms.CharField( + widget=forms.Textarea(attrs={'rows': 4}), + required=False)) + public = tables.Column("is_public", + verbose_name=_("Public"), + filters=(filters.yesno, filters.capfirst), + form_field=forms.BooleanField( + label=_('Public'), required=False)) + + def get_object_display(self, group_type): + return group_type.name + + def get_object_id(self, group_type): + return str(group_type.id) + + class Meta(object): + name = "group_types" + verbose_name = _("Group Types") + table_actions = ( + GroupTypesFilterAction, + CreateGroupType, + DeleteGroupType, + ) + row_actions = ( + EditGroupType, + DeleteGroupType + ) + row_class = UpdateRow diff --git a/openstack_dashboard/dashboards/admin/group_types/templates/group_types/_create_group_type.html b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/_create_group_type.html new file mode 100644 index 0000000000..7474fdce1c --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/_create_group_type.html @@ -0,0 +1,16 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +
{% blocktrans trimmed %} + Group type is a type or label that can be selected at group creation + time in OpenStack. It usually maps to a set of capabilities of the storage + back-end driver to be used for this volume. Examples: "Performance", + "SSD", "Backup", etc. This is equivalent to the + cinder type-create command. Once the group type gets created, + click the "Extra Specs" button to set up extra specs key-value + pair(s) for that group type. + {% endblocktrans %} +
+{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/group_types/templates/group_types/_update_group_type.html b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/_update_group_type.html new file mode 100644 index 0000000000..aa49a7e251 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/group_types/templates/group_types/_update_group_type.html @@ -0,0 +1,20 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block form_id %}{% endblock %} +{% block form_action %}{% url 'horizon:admin:group_types:update_type' group_type.id %}{% endblock %} + +{% block modal_id %}update_group_type_modal{% endblock %} +{% block modal-header %}{% trans "Edit Group Type" %}{% endblock %} + +{% block modal-body %} +{% trans "Modify group type name, description, and public status." %}
+