From c6e74073ca4aa7db29c31554a6dc11cd3ca7a129 Mon Sep 17 00:00:00 2001 From: Matt Van Dijk Date: Thu, 14 Jan 2016 10:28:26 -0500 Subject: [PATCH] Add support for Trove create database Added a table action on the databases table that displays a create database dialog. Co-Authored-By: Duk Loi Change-Id: I092629080987bcdd8c54e052be2f7fa9b84259a0 Implements: blueprint trove-create-database-support --- trove_dashboard/api/trove.py | 10 +++ trove_dashboard/content/databases/forms.py | 27 ++++++++ trove_dashboard/content/databases/tables.py | 26 ++++++- trove_dashboard/content/databases/tabs.py | 6 +- .../templates/databases/_create_database.html | 8 +++ .../templates/databases/create_database.html | 6 ++ trove_dashboard/content/databases/tests.py | 69 ++++++++++++++++++- trove_dashboard/content/databases/urls.py | 2 + trove_dashboard/content/databases/views.py | 26 +++++++ 9 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 trove_dashboard/content/databases/templates/databases/_create_database.html create mode 100644 trove_dashboard/content/databases/templates/databases/create_database.html diff --git a/trove_dashboard/api/trove.py b/trove_dashboard/api/trove.py index e509e13..95c7ea8 100644 --- a/trove_dashboard/api/trove.py +++ b/trove_dashboard/api/trove.py @@ -155,6 +155,16 @@ def database_list(request, instance_id): return troveclient(request).databases.list(instance_id) +def database_create(request, instance_id, db_name, character_set=None, + collation=None): + database = {'name': db_name} + if collation: + database['collate'] = collation + if character_set: + database['character_set'] = character_set + return troveclient(request).databases.create(instance_id, [database]) + + def database_delete(request, instance_id, db_name): return troveclient(request).databases.delete(instance_id, db_name) diff --git a/trove_dashboard/content/databases/forms.py b/trove_dashboard/content/databases/forms.py index 463e417..3b6962c 100644 --- a/trove_dashboard/content/databases/forms.py +++ b/trove_dashboard/content/databases/forms.py @@ -24,6 +24,33 @@ from horizon.utils import validators from trove_dashboard import api +class CreateDatabaseForm(forms.SelfHandlingForm): + instance_id = forms.CharField(widget=forms.HiddenInput()) + name = forms.CharField(label=_("Name")) + character_set = forms.CharField( + label=_("Character Set"), required=False, + help_text=_("Optional character set for the database.")) + collation = forms.CharField( + label=_("Collation"), required=False, + help_text=_("Optional collation type for the database.")) + + def handle(self, request, data): + instance = data.get('instance_id') + try: + api.trove.database_create(request, instance, data['name'], + character_set=data['character_set'], + collation=data['collation']) + + messages.success(request, + _('Created database "%s".') % data['name']) + except Exception as e: + redirect = reverse("horizon:project:databases:detail", + args=(instance,)) + exceptions.handle(request, _('Unable to create database. %s') % + e.message, redirect=redirect) + return True + + class ResizeVolumeForm(forms.SelfHandlingForm): instance_id = forms.CharField(widget=forms.HiddenInput()) orig_size = forms.IntegerField( diff --git a/trove_dashboard/content/databases/tables.py b/trove_dashboard/content/databases/tables.py index 705c116..d012e37 100644 --- a/trove_dashboard/content/databases/tables.py +++ b/trove_dashboard/content/databases/tables.py @@ -306,6 +306,30 @@ class DeleteUser(tables.DeleteAction): api.trove.user_delete(request, datum.instance.id, datum.name) +class CreateDatabase(tables.LinkAction): + name = "create_database" + verbose_name = _("Create Database") + url = "horizon:project:databases:create_database" + classes = ("ajax-modal",) + icon = "plus" + + def allowed(self, request, database=None): + instance = self.table.kwargs['instance'] + return (instance.status in ACTIVE_STATES and + has_database_add_perm(request)) + + def get_link_url(self, datum=None): + instance_id = self.table.kwargs['instance_id'] + return urlresolvers.reverse(self.url, args=[instance_id]) + + +def has_database_add_perm(request): + perms = getattr(settings, 'TROVE_ADD_DATABASE_PERMS', []) + if perms: + return request.user.has_perms(perms) + return True + + class DeleteDatabase(tables.DeleteAction): @staticmethod def action_present(count): @@ -533,7 +557,7 @@ class DatabaseTable(tables.DataTable): class Meta(object): name = "databases" verbose_name = _("Databases") - table_actions = [DeleteDatabase] + table_actions = [CreateDatabase, DeleteDatabase] row_actions = [DeleteDatabase] def get_object_id(self, datum): diff --git a/trove_dashboard/content/databases/tabs.py b/trove_dashboard/content/databases/tabs.py index b527522..de0942b 100644 --- a/trove_dashboard/content/databases/tabs.py +++ b/trove_dashboard/content/databases/tabs.py @@ -12,7 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -from django.conf import settings from django import template from django.utils.translation import ugettext_lazy as _ @@ -96,10 +95,7 @@ class DatabaseTab(tabs.TableTab): return data def allowed(self, request): - perms = getattr(settings, 'TROVE_ADD_DATABASE_PERMS', []) - if perms: - return request.user.has_perms(perms) - return True + return tables.has_database_add_perm(request) class BackupsTab(tabs.TableTab): diff --git a/trove_dashboard/content/databases/templates/databases/_create_database.html b/trove_dashboard/content/databases/templates/databases/_create_database.html new file mode 100644 index 0000000..4d609d4 --- /dev/null +++ b/trove_dashboard/content/databases/templates/databases/_create_database.html @@ -0,0 +1,8 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Specify the name of the new database." %}

+

{% trans "Optionally provide a character set and collation for the database." %}

+{% endblock %} + diff --git a/trove_dashboard/content/databases/templates/databases/create_database.html b/trove_dashboard/content/databases/templates/databases/create_database.html new file mode 100644 index 0000000..eda34c6 --- /dev/null +++ b/trove_dashboard/content/databases/templates/databases/create_database.html @@ -0,0 +1,6 @@ +{% extends 'base.html' %} + +{% block main %} + {% include "project/databases/_create_database.html" %} +{% endblock %} + diff --git a/trove_dashboard/content/databases/tests.py b/trove_dashboard/content/databases/tests.py index bd6d8f0..4d2d416 100644 --- a/trove_dashboard/content/databases/tests.py +++ b/trove_dashboard/content/databases/tests.py @@ -18,7 +18,7 @@ import logging import django from django.core.urlresolvers import reverse from django import http -from django.utils import unittest +import unittest from mox3.mox import IsA # noqa import six @@ -333,6 +333,73 @@ class DatabaseTests(test.TestCase): database = self.databases.list()[1] self._test_details(database, with_designate=True) + def test_create_database(self): + database = self.databases.first() + + url = reverse('horizon:project:databases:create_database', + args=[database.id]) + res = self.client.get(url) + self.assertTemplateUsed(res, 'project/databases/create_database.html') + + @test.create_stubs({api.trove: ('database_create',)}) + def test_create_new_database(self): + new_database = { + "status": "ACTIVE", + "updated": "2013-08-12T22:00:09", + "name": "NewDB", + "links": [], + "created": "2013-08-12T22:00:03", + "ip": [ + "10.0.0.3", + ], + "volume": { + "used": 0.13, + "size": 1, + }, + "flavor": { + "id": "1", + "links": [], + }, + "datastore": { + "type": "mysql", + "version": "5.5" + }, + "id": "12345678-73db-4e23-b52e-368937d72719", + } + + api.trove.database_create( + IsA(http.HttpRequest), u'id', u'NewDB', character_set=u'', + collation=u'').AndReturn(new_database) + self.mox.ReplayAll() + + url = reverse('horizon:project:databases:create_database', + args=['id']) + post = { + 'method': 'CreateDatabaseForm', + 'instance_id': 'id', + 'name': 'NewDB'} + + res = self.client.post(url, post) + self.assertNoFormErrors(res) + self.assertMessageCount(success=1) + + @test.create_stubs({api.trove: ('database_create',)}) + def test_create_new_database_exception(self): + api.trove.database_create( + IsA(http.HttpRequest), u'id', u'NewDB', character_set=u'', + collation=u'').AndRaise(self.exceptions.trove) + self.mox.ReplayAll() + + url = reverse('horizon:project:databases:create_database', + args=['id']) + post = { + 'method': 'CreateDatabaseForm', + 'instance_id': 'id', + 'name': 'NewDB'} + + res = self.client.post(url, post) + self.assertEqual(res.status_code, 302) + @test.create_stubs( {api.trove: ('instance_get', 'flavor_get', 'users_list', 'user_list_access', 'user_delete')}) diff --git a/trove_dashboard/content/databases/urls.py b/trove_dashboard/content/databases/urls.py index f3f7fc3..ac2dd01 100644 --- a/trove_dashboard/content/databases/urls.py +++ b/trove_dashboard/content/databases/urls.py @@ -37,4 +37,6 @@ urlpatterns = patterns( name='edit_user'), url(USERS % 'access_detail', views.AccessDetailView.as_view(), name='access_detail'), + url(INSTANCES % 'create_database', views.CreateDatabaseView.as_view(), + name='create_database'), ) diff --git a/trove_dashboard/content/databases/views.py b/trove_dashboard/content/databases/views.py index 2a2f4c6..56aaa3f 100644 --- a/trove_dashboard/content/databases/views.py +++ b/trove_dashboard/content/databases/views.py @@ -248,6 +248,32 @@ class DetailView(horizon_tabs.TabbedTableView): return reverse('horizon:project:databases:index') +class CreateDatabaseView(horizon_forms.ModalFormView): + form_class = forms.CreateDatabaseForm + form_id = "create_database_form" + modal_header = _("Create Database") + modal_id = "create_database_modal" + template_name = 'project/databases/create_database.html' + submit_label = _("Create Database") + submit_url = 'horizon:project:databases:create_database' + success_url = 'horizon:project:databases:detail' + + def get_success_url(self): + return reverse(self.success_url, + args=(self.kwargs['instance_id'],)) + + def get_context_data(self, **kwargs): + context = super(CreateDatabaseView, self).get_context_data(**kwargs) + context['instance_id'] = self.kwargs['instance_id'] + args = (self.kwargs['instance_id'],) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + def get_initial(self): + instance_id = self.kwargs['instance_id'] + return {'instance_id': instance_id} + + class ResizeVolumeView(horizon_forms.ModalFormView): form_class = forms.ResizeVolumeForm template_name = 'project/databases/resize_volume.html'