Inline object creation.

Allows the creation of related objects during a workflow.
For example, this patch implements importing keypairs during
the launch instance workflow and allocating floating IP
addresses during the floating IP associate workflow.

This required several significant changes:

  * SelfHandlingForm should no long return a redirect.
    Instead, it should return either the object it
    created/acted on, or else a boolean such as True.

  * The ModalFormView now differentiates between GET
    and POST.

  * Due to the previous two items, SelfHandlingForm
    was mostly gutted (no more maybe_handle, etc.).

  * Modals now operate via a "stack" where only the
    top modal is visible at any given time and closing
    one causes the next one to become visible.

In the process of these large changes there was a large
amount of general code cleanup, especially in the javascript
code and the existing SelfHandlingForm subclasses/ModalFormView
subclasses. Many small bugs were fixed along with the cleanup.

Implements blueprint inline-object-creation.

Fixes bug 994677.
Fixes bug 1025977.
Fixes bug 1027342.
Fixes bug 1025919.

Change-Id: I1808b34cbf6f813eaedf767a6364e815c0c5e969
This commit is contained in:
Gabriel Hurley 2012-07-13 16:42:28 -07:00
parent 76246c6b18
commit df5a13c5ec
119 changed files with 968 additions and 803 deletions

View File

@ -364,7 +364,12 @@ def server_reboot(request, instance_id, hardness=REBOOT_HARD):
def server_update(request, instance_id, name):
return novaclient(request).servers.update(instance_id, name=name)
response = novaclient(request).servers.update(instance_id, name=name)
# TODO(gabriel): servers.update method doesn't return anything. :-(
if response is None:
return True
else:
return response
def server_add_floating_ip(request, server, floating_ip):

View File

@ -19,22 +19,15 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django.contrib import messages
from django import shortcuts
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import exceptions
from horizon import forms
LOG = logging.getLogger(__name__)
from horizon import messages
class FloatingIpAllocate(forms.SelfHandlingForm):
tenant_name = forms.CharField(widget=forms.HiddenInput())
pool = forms.ChoiceField(label=_("Pool"))
def __init__(self, *args, **kwargs):
@ -44,16 +37,10 @@ class FloatingIpAllocate(forms.SelfHandlingForm):
def handle(self, request, data):
try:
fip = api.tenant_floating_ip_allocate(request,
pool=data.get('pool', None))
LOG.info('Allocating Floating IP "%s" to project "%s"'
% (fip.ip, data['tenant_name']))
fip = api.tenant_floating_ip_allocate(request, pool=data['pool'])
messages.success(request,
_('Successfully allocated Floating IP "%(ip)s" '
'to project "%(project)s"')
% {"ip": fip.ip, "project": data['tenant_name']})
_('Allocated Floating IP %(ip)s.')
% {"ip": fip.ip})
return fip
except:
exceptions.handle(request, _('Unable to allocate Floating IP.'))
return shortcuts.redirect(
'horizon:nova:access_and_security:index')

View File

@ -18,13 +18,13 @@
import logging
from django import shortcuts
from django.contrib import messages
from django.core import urlresolvers
from django.utils.http import urlencode
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import exceptions
from horizon import messages
from horizon import tables

View File

@ -23,6 +23,7 @@
Views for managing Nova floating IPs.
"""
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import api
@ -41,7 +42,10 @@ class AssociateView(workflows.WorkflowView):
class AllocateView(forms.ModalFormView):
form_class = FloatingIpAllocate
template_name = 'nova/access_and_security/floating_ips/allocate.html'
context_object_name = 'floating_ip'
success_url = reverse_lazy('horizon:nova:access_and_security:index')
def get_object_display(self, obj):
return obj.ip
def get_context_data(self, **kwargs):
context = super(AllocateView, self).get_context_data(**kwargs)
@ -52,11 +56,13 @@ class AllocateView(forms.ModalFormView):
return context
def get_initial(self):
pools = api.floating_ip_pools_list(self.request)
if pools:
pool_list = [(pool.name, pool.name)
for pool in api.floating_ip_pools_list(self.request)]
else:
try:
pools = api.floating_ip_pools_list(self.request)
except:
pools = []
exceptions.handle(self.request,
_("Unable to retrieve floating IP pools."))
pool_list = [(pool.name, pool.name) for pool in pools]
if not pool_list:
pool_list = [(None, _("No floating IP pools available."))]
return {'tenant_name': self.request.user.tenant_name,
'pool_list': pool_list}
return {'pool_list': pool_list}

View File

@ -15,19 +15,23 @@
# License for the specific language governing permissions and limitations
# under the License.
from django import forms
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import exceptions
from horizon import workflows
from horizon import forms
ALLOCATE_URL = "horizon:nova:access_and_security:floating_ips:allocate"
class AssociateIPAction(workflows.Action):
ip_id = forms.TypedChoiceField(label=_("IP Address"),
coerce=int,
empty_value=None)
ip_id = forms.DynamicTypedChoiceField(label=_("IP Address"),
coerce=int,
empty_value=None,
add_item_link=ALLOCATE_URL)
instance_id = forms.ChoiceField(label=_("Instance"))
class Meta:

View File

@ -18,20 +18,18 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
import re
from django import shortcuts
from django.contrib import messages
from django.core import validators
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import exceptions
from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__)
NEW_LINES = re.compile(r"\r|\n")
@ -44,14 +42,7 @@ class CreateKeypair(forms.SelfHandlingForm):
'and hyphens.')})
def handle(self, request, data):
try:
return shortcuts.redirect(
'horizon:nova:access_and_security:keypairs:download',
keypair_name=data['name'])
except:
exceptions.handle(request,
_('Unable to create keypair.'))
return shortcuts.redirect(request.build_absolute_uri())
return True # We just redirect to the download view.
class ImportKeypair(forms.SelfHandlingForm):
@ -61,14 +52,14 @@ class ImportKeypair(forms.SelfHandlingForm):
def handle(self, request, data):
try:
LOG.info('Importing keypair "%s"' % data['name'])
# Remove any new lines in the public key
data['public_key'] = NEW_LINES.sub("", data['public_key'])
api.keypair_import(request, data['name'], data['public_key'])
keypair = api.keypair_import(request,
data['name'],
data['public_key'])
messages.success(request, _('Successfully imported public key: %s')
% data['name'])
return shortcuts.redirect(
'horizon:nova:access_and_security:index')
return keypair
except:
exceptions.handle(request,
_('Unable to import keypair.'))

View File

@ -124,8 +124,8 @@ class KeyPairViewTests(test.TestCase):
'name': key1_name,
'public_key': public_key}
url = reverse('horizon:nova:access_and_security:keypairs:import')
self.client.post(url, formData)
self.assertMessageCount(success=1)
res = self.client.post(url, formData)
self.assertMessageCount(res, success=1)
def test_generate_keypair_exception(self):
keypair = self.keypairs.first()

View File

@ -24,7 +24,7 @@ Views for managing Nova keypairs.
import logging
from django import http
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse, reverse_lazy
from django.template.defaultfilters import slugify
from django.views.generic import View, TemplateView
from django.utils.translation import ugettext_lazy as _
@ -41,11 +41,20 @@ LOG = logging.getLogger(__name__)
class CreateView(forms.ModalFormView):
form_class = CreateKeypair
template_name = 'nova/access_and_security/keypairs/create.html'
success_url = 'horizon:nova:access_and_security:keypairs:download'
def get_success_url(self):
return reverse(self.success_url,
kwargs={"keypair_name": self.request.POST['name']})
class ImportView(forms.ModalFormView):
form_class = ImportKeypair
template_name = 'nova/access_and_security/keypairs/import.html'
success_url = reverse_lazy('horizon:nova:access_and_security:index')
def get_object_id(self, keypair):
return keypair.name
class DownloadView(TemplateView):

View File

@ -18,24 +18,19 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django import shortcuts
from django.contrib import messages
from django.core import validators
from django.core.urlresolvers import reverse
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import exceptions
from horizon import forms
from horizon import messages
from horizon.utils.validators import validate_port_range
from horizon.utils import fields
LOG = logging.getLogger(__name__)
class CreateGroup(forms.SelfHandlingForm):
name = forms.CharField(label=_("Name"),
validators=[validators.validate_slug])
@ -43,15 +38,18 @@ class CreateGroup(forms.SelfHandlingForm):
def handle(self, request, data):
try:
api.security_group_create(request,
data['name'],
data['description'])
sg = api.security_group_create(request,
data['name'],
data['description'])
messages.success(request,
_('Successfully created security group: %s')
% data['name'])
% data['name'])
return sg
except:
exceptions.handle(request, _('Unable to create security group.'))
return shortcuts.redirect('horizon:nova:access_and_security:index')
redirect = reverse("horizon:nova:access_and_security:index")
exceptions.handle(request,
_('Unable to create security group.'),
redirect=redirect)
class AddRule(forms.SelfHandlingForm):
@ -59,6 +57,8 @@ class AddRule(forms.SelfHandlingForm):
choices=[('tcp', 'TCP'),
('udp', 'UDP'),
('icmp', 'ICMP')],
help_text=_("The protocol which this "
"rule should be applied to."),
widget=forms.Select(attrs={'class':
'switchable'}))
from_port = forms.IntegerField(label=_("From Port"),
@ -80,7 +80,13 @@ class AddRule(forms.SelfHandlingForm):
'data-icmp': _('Code')}),
validators=[validate_port_range])
source_group = forms.ChoiceField(label=_('Source Group'), required=False)
source_group = forms.ChoiceField(label=_('Source Group'),
required=False,
help_text=_("To specify an allowed IP "
"range, select CIDR. To "
"allow access from all "
"members of another security "
"group select Source Group."))
cidr = fields.IPField(label=_("CIDR"),
required=False,
initial="0.0.0.0/0",
@ -92,14 +98,13 @@ class AddRule(forms.SelfHandlingForm):
security_group_id = forms.IntegerField(widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
sg_list = kwargs.pop('sg_list', [])
super(AddRule, self).__init__(*args, **kwargs)
initials = kwargs.get("initial", {})
current_group_id = initials.get('security_group_id', 0)
security_groups = initials.get('security_group_list', [])
security_groups_choices = [("", "CIDR")] # default choice is CIDR
group_choices = [s for s in security_groups]
if len(group_choices): # add group choice if available
security_groups_choices.append(('Security Group', group_choices))
# Determine if there are security groups available for the
# source group option; add the choices and enable the option if so.
security_groups_choices = [("", "CIDR")]
if sg_list:
security_groups_choices.append(('Security Group', sg_list))
self.fields['source_group'].choices = security_groups_choices
def clean(self):
@ -160,7 +165,9 @@ class AddRule(forms.SelfHandlingForm):
data['source_group'])
messages.success(request,
_('Successfully added rule: %s') % unicode(rule))
return rule
except:
redirect = reverse("horizon:nova:access_and_security:index")
exceptions.handle(request,
_('Unable to add rule to security group.'))
return shortcuts.redirect("horizon:nova:access_and_security:index")
_('Unable to add rule to security group.'),
redirect=redirect)

View File

@ -100,30 +100,16 @@ class SecurityGroupsViewTests(test.TestCase):
def test_edit_rules_get_exception(self):
sec_group = self.security_groups.first()
sec_group_list = self.security_groups.list()
self.mox.StubOutWithMock(api, 'security_group_get')
self.mox.StubOutWithMock(api, 'security_group_list')
self.mox.StubOutWithMock(api, 'tenant_floating_ip_list')
self.mox.StubOutWithMock(api.nova, 'keypair_list')
self.mox.StubOutWithMock(api.nova, 'server_list')
api.nova.server_list(IsA(http.HttpRequest),
all_tenants=True).AndReturn(self.servers.list())
api.nova.keypair_list(IsA(http.HttpRequest)) \
.AndReturn(self.keypairs.list())
api.tenant_floating_ip_list(IsA(http.HttpRequest)) \
.AndReturn(self.floating_ips.list())
api.security_group_get(IsA(http.HttpRequest),
sec_group.id).AndRaise(self.exceptions.nova)
api.security_group_list(
IsA(http.HttpRequest)).AndReturn(sec_group_list)
api.security_group_list(
IsA(http.HttpRequest)).AndReturn(sec_group_list)
self.mox.ReplayAll()
res = self.client.get(self.edit_url)
self.assertRedirects(res, INDEX_URL)
self.assertRedirectsNoFollow(res, INDEX_URL)
def test_edit_rules_add_rule_cidr(self):
sec_group = self.security_groups.first()

View File

@ -24,6 +24,7 @@ Views for managing Nova instances.
import logging
from django import shortcuts
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import api
@ -37,9 +38,11 @@ from .tables import RulesTable
LOG = logging.getLogger(__name__)
class EditRulesView(tables.DataTableView):
class EditRulesView(tables.DataTableView, forms.ModalFormView):
table_class = RulesTable
form_class = AddRule
template_name = 'nova/access_and_security/security_groups/edit_rules.html'
success_url = reverse_lazy("horizon:nova:access_and_security:index")
def get_data(self):
security_group_id = int(self.kwargs['security_group_id'])
@ -55,7 +58,12 @@ class EditRulesView(tables.DataTableView):
_('Unable to retrieve security group.'))
return rules
def handle_form(self):
def get_initial(self):
return {'security_group_id': self.kwargs['security_group_id']}
def get_form_kwargs(self):
kwargs = super(EditRulesView, self).get_form_kwargs()
try:
groups = api.security_group_list(self.request)
except:
@ -63,36 +71,49 @@ class EditRulesView(tables.DataTableView):
exceptions.handle(self.request,
_("Unable to retrieve security groups."))
security_groups = [(group.id, group.name) for group in groups]
security_groups = []
for group in groups:
if group.id == int(self.kwargs['security_group_id']):
security_groups.append((group.id,
_("%s (current)") % group.name))
else:
security_groups.append((group.id, group.name))
kwargs['sg_list'] = security_groups
return kwargs
initial = {'security_group_id': self.kwargs['security_group_id'],
'security_group_list': security_groups}
return AddRule.maybe_handle(self.request, initial=initial)
def get_form(self):
if not hasattr(self, "_form"):
form_class = self.get_form_class()
self._form = super(EditRulesView, self).get_form(form_class)
return self._form
def get_context_data(self, **kwargs):
context = super(EditRulesView, self).get_context_data(**kwargs)
context['form'] = self.get_form()
if self.request.is_ajax():
context['hide'] = True
return context
def get(self, request, *args, **kwargs):
# Form handling
form, handled = self.handle_form()
if handled:
return handled
# Table action handling
handled = self.construct_tables()
if handled:
return handled
if not self.object:
return shortcuts.redirect("horizon:nova:access_and_security:index")
if not self.object: # Set during table construction.
return shortcuts.redirect(self.success_url)
context = self.get_context_data(**kwargs)
context['form'] = form
context['security_group'] = self.object
if request.is_ajax():
context['hide'] = True
self.template_name = ('nova/access_and_security/security_groups'
'/_edit_rules.html')
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.get(request, *args, **kwargs)
class CreateView(forms.ModalFormView):
form_class = CreateGroup
template_name = 'nova/access_and_security/security_groups/create.html'
def get_initial(self):
return {"tenant_id": self.request.user.tenant_id}
success_url = reverse_lazy('horizon:nova:access_and_security:index')

View File

@ -1,7 +1,7 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Allocate Floating IP" %}{% endblock %}
{% block dash_main %}
{% block main %}
{% include 'nova/access_and_security/floating_ips/_allocate.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Associate Floating IP" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Associate Floating IP") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Access & Security{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title=_("Access & Security") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
<div id="floating_ips">
{{ floating_ips_table.render }}
</div>

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Create Keypair{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title=_("Create Keypair") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'nova/access_and_security/keypairs/_create.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% blocktrans %}Download Keypair{% endblocktrans %}{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title=_("Download Keypair") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
<div class="modal-header">
<h3>{% blocktrans %}The keypair &quot;{{ keypair_name }}&quot; should download automatically. If not use the link below.{% endblocktrans %}</h3>
</div>

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Import Keypair{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title=_("Import Keypair") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'nova/access_and_security/keypairs/_import.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Create Security Group{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Create Security Group") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'nova/access_and_security/security_groups/_create.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Edit Security Group Rules{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Edit Security Group Rules") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include "nova/access_and_security/security_groups/_edit_rules.html" %}
{% endblock %}

View File

@ -24,7 +24,6 @@ Views for Instances and Volumes.
"""
import logging
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from horizon import api

View File

@ -20,8 +20,6 @@
import logging
from django import shortcuts
from django.contrib import messages
from django.core import validators
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
@ -29,6 +27,7 @@ from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import exceptions
from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__)
@ -65,17 +64,10 @@ class CreateContainer(forms.SelfHandlingForm):
container,
subfolder_name)
messages.success(request, _("Folder created successfully."))
url = "horizon:nova:containers:object_index"
if remainder:
remainder = remainder.rstrip("/")
remainder += "/"
return shortcuts.redirect(url, container, remainder)
return True
except:
exceptions.handle(request, _('Unable to create container.'))
return shortcuts.redirect("horizon:nova:containers:index")
class UploadObject(forms.SelfHandlingForm):
path = forms.CharField(max_length=255,
@ -101,10 +93,9 @@ class UploadObject(forms.SelfHandlingForm):
obj.metadata['orig-filename'] = object_file.name
obj.sync_metadata()
messages.success(request, _("Object was successfully uploaded."))
return obj
except:
exceptions.handle(request, _("Unable to upload object."))
return shortcuts.redirect("horizon:nova:containers:object_index",
data['container_name'], data['path'])
class CopyObject(forms.SelfHandlingForm):
@ -160,12 +151,13 @@ class CopyObject(forms.SelfHandlingForm):
messages.success(request,
_('Copied "%(orig)s" to "%(dest)s" as "%(new)s".')
% vals)
return True
except exceptions.HorizonException, exc:
messages.error(request, exc)
return shortcuts.redirect(object_index, orig_container)
raise exceptions.Http302(reverse(object_index,
args=[orig_container]))
except:
redirect = reverse(object_index, args=(orig_container,))
exceptions.handle(request,
_("Unable to copy object."),
redirect=redirect)
return shortcuts.redirect(object_index, new_container, data['path'])

View File

@ -17,13 +17,13 @@
import logging
from cloudfiles.errors import ContainerNotEmpty
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.template.defaultfilters import filesizeformat
from django.utils import http
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import messages
from horizon import tables

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Copy Object" %}{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title=_("Copy Object") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'nova/containers/_copy.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Create Container{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Create Container") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include "nova/containers/_create.html" %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Objects" %}{% endblock %}
@ -8,7 +8,7 @@
</div>
{% endblock page_header %}
{% block dash_main %}
{% block main %}
<div id="subfolders">
{{ subfolders_table.render }}
</div>

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Containers{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Containers") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Upload Object" %}{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title=_("Upload Objects") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'nova/containers/_upload.html' %}
{% endblock %}

View File

@ -93,7 +93,9 @@ class ContainerViewTests(test.TestCase):
'method': forms.CreateContainer.__name__}
res = self.client.post(reverse('horizon:nova:containers:create'),
formData)
self.assertRedirectsNoFollow(res, CONTAINER_INDEX_URL)
url = reverse('horizon:nova:containers:object_index',
args=[self.containers.first().name])
self.assertRedirectsNoFollow(res, url)
class ObjectViewTests(test.TestCase):

View File

@ -63,6 +63,17 @@ class IndexView(tables.DataTableView):
class CreateView(forms.ModalFormView):
form_class = CreateContainer
template_name = 'nova/containers/create.html'
success_url = "horizon:nova:containers:object_index"
def get_success_url(self):
parent = self.request.POST.get('parent', None)
if parent:
container, slash, remainder = parent.partition("/")
if remainder and not remainder.endswith("/"):
remainder = "".join([remainder, "/"])
return reverse(self.success_url, args=(container, remainder))
else:
return reverse(self.success_url, args=[self.request.POST['name']])
def get_initial(self):
initial = super(CreateView, self).get_initial()
@ -132,6 +143,12 @@ class ObjectIndexView(tables.MultiTableView):
class UploadView(forms.ModalFormView):
form_class = UploadObject
template_name = 'nova/containers/upload.html'
success_url = "horizon:nova:containers:object_index"
def get_success_url(self):
return reverse(self.success_url,
args=(self.request.POST['container_name'],
self.request.POST.get('path', '')))
def get_initial(self):
return {"container_name": self.kwargs["container_name"],
@ -172,6 +189,12 @@ def object_download(request, container_name, object_path):
class CopyView(forms.ModalFormView):
form_class = CopyObject
template_name = 'nova/containers/copy.html'
success_url = "horizon:nova:containers:object_index"
def get_success_url(self):
return reverse(self.success_url,
args=(self.request.POST['new_container_name'],
self.request.POST.get('path', '')))
def get_form_kwargs(self):
kwargs = super(CopyView, self).get_form_kwargs()

View File

@ -24,26 +24,23 @@ Views for managing Nova images.
import logging
from django import shortcuts
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import exceptions
from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__)
class CreateImageForm(forms.SelfHandlingForm):
completion_view = 'horizon:nova:images_and_snapshots:index'
name = forms.CharField(max_length="255", label=_("Name"), required=True)
copy_from = forms.CharField(max_length="255",
label=_("Image Location"),
help_text=_("An external (HTTP) URL where"
" the image should be loaded from."),
help_text=_("An external (HTTP) URL to load "
"the image from."),
required=True)
disk_format = forms.ChoiceField(label=_('Format'),
required=True,
@ -103,19 +100,16 @@ class CreateImageForm(forms.SelfHandlingForm):
'name': data['name']}
try:
api.glance.image_create(request, **meta)
image = api.glance.image_create(request, **meta)
messages.success(request,
_('Your image %s has been queued for creation.' %
data['name']))
return image
except:
exceptions.handle(request, _('Unable to create new image.'))
return shortcuts.redirect(self.get_success_url())
class UpdateImageForm(forms.SelfHandlingForm):
completion_view = 'horizon:nova:images_and_snapshots:index'
image_id = forms.CharField(widget=forms.HiddenInput())
name = forms.CharField(max_length="255", label=_("Name"))
kernel = forms.CharField(max_length="36", label=_("Kernel ID"),
@ -139,13 +133,17 @@ class UpdateImageForm(forms.SelfHandlingForm):
public = forms.BooleanField(label=_("Public"), required=False)
def handle(self, request, data):
# TODO add public flag to image meta properties
image_id = data['image_id']
error_updating = _('Unable to update image "%s".')
if data['disk_format'] in ['aki', 'ari', 'ami']:
container_format = data['disk_format']
else:
container_format = 'bare'
meta = {'is_public': data['public'],
'disk_format': data['disk_format'],
'container_format': 'bare',
'container_format': container_format,
'name': data['name'],
'properties': {}}
if data['kernel']:
@ -154,13 +152,13 @@ class UpdateImageForm(forms.SelfHandlingForm):
meta['properties']['ramdisk_id'] = data['ramdisk']
if data['architecture']:
meta['properties']['architecture'] = data['architecture']
# Ensure we do not delete properties that have already been
# set on an image.
meta['purge_props'] = False
try:
# Ensure we do not delete properties that have already been
# set on an image.
meta['features'] = {'X-Glance-Registry-Purge-Props': False}
api.image_update(request, image_id, **meta)
image = api.image_update(request, image_id, **meta)
messages.success(request, _('Image was successfully updated.'))
return image
except:
exceptions.handle(request, error_updating % image_id)
return shortcuts.redirect(self.get_success_url())

View File

@ -24,7 +24,7 @@ Views for managing Nova images.
import logging
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import api
@ -43,35 +43,39 @@ class CreateView(forms.ModalFormView):
form_class = CreateImageForm
template_name = 'nova/images_and_snapshots/images/create.html'
context_object_name = 'image'
success_url = reverse_lazy("horizon:nova:images_and_snapshots:index")
class UpdateView(forms.ModalFormView):
form_class = UpdateImageForm
template_name = 'nova/images_and_snapshots/images/update.html'
context_object_name = 'image'
success_url = reverse_lazy("horizon:nova:images_and_snapshots:index")
def get_object(self, *args, **kwargs):
try:
self.object = api.image_get(self.request, kwargs['image_id'])
except:
msg = _('Unable to retrieve image.')
redirect = reverse('horizon:nova:images_and_snapshots:index')
exceptions.handle(self.request, msg, redirect=redirect)
return self.object
def get_object(self):
if not hasattr(self, "_object"):
try:
self._object = api.image_get(self.request,
self.kwargs['image_id'])
except:
msg = _('Unable to retrieve image.')
redirect = reverse('horizon:nova:images_and_snapshots:index')
exceptions.handle(self.request, msg, redirect=redirect)
return self._object
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
context['image'] = self.get_object()
return context
def get_initial(self):
properties = self.object.properties
# NOTE(gabriel): glanceclient currently treats "is_public" as a string
# rather than a boolean. This should be fixed in the client.
public = self.object.is_public == "True"
image = self.get_object()
return {'image_id': self.kwargs['image_id'],
'name': self.object.name,
'kernel': properties.get('kernel_id', ''),
'ramdisk': properties.get('ramdisk_id', ''),
'architecture': properties.get('architecture', ''),
'container_format': self.object.container_format,
'disk_format': self.object.disk_format,
'public': public}
'name': image.name,
'kernel': image.properties.get('kernel_id', ''),
'ramdisk': image.properties.get('ramdisk_id', ''),
'architecture': image.properties.get('architecture', ''),
'disk_format': image.disk_format,
'public': image.is_public == "True"}
class DetailView(tabs.TabView):

View File

@ -20,21 +20,19 @@
import logging
from django import shortcuts
from django.core.urlresolvers import reverse
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import exceptions
from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__)
class CreateSnapshot(forms.SelfHandlingForm):
tenant_id = forms.CharField(widget=forms.HiddenInput())
instance_id = forms.CharField(label=_("Instance ID"),
widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
@ -42,14 +40,15 @@ class CreateSnapshot(forms.SelfHandlingForm):
def handle(self, request, data):
try:
api.snapshot_create(request, data['instance_id'], data['name'])
snapshot = api.snapshot_create(request,
data['instance_id'],
data['name'])
# NOTE(gabriel): This API call is only to display a pretty name.
instance = api.server_get(request, data['instance_id'])
vals = {"name": data['name'], "inst": instance.name}
messages.success(request, _('Snapshot "%(name)s" created for '
'instance "%(inst)s"') % vals)
return shortcuts.redirect('horizon:nova:images_and_snapshots:'
'index')
return snapshot
except:
redirect = reverse("horizon:nova:instances:index")
exceptions.handle(request,

View File

@ -42,18 +42,6 @@ class SnapshotsViewTests(test.TestCase):
self.assertTemplateUsed(res,
'nova/images_and_snapshots/snapshots/create.html')
def test_create_snapshot_get_with_invalid_status(self):
server = self.servers.get(status='BUILD')
self.mox.StubOutWithMock(api, 'server_get')
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
self.mox.ReplayAll()
url = reverse('horizon:nova:images_and_snapshots:snapshots:create',
args=[server.id])
res = self.client.get(url)
redirect = reverse("horizon:nova:instances:index")
self.assertRedirectsNoFollow(res, redirect)
def test_create_get_server_exception(self):
server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_get')
@ -76,7 +64,6 @@ class SnapshotsViewTests(test.TestCase):
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.snapshot_create(IsA(http.HttpRequest), server.id, snapshot.name) \
.AndReturn(snapshot)
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
self.mox.ReplayAll()
formData = {'method': 'CreateSnapshot',
@ -95,7 +82,6 @@ class SnapshotsViewTests(test.TestCase):
self.mox.StubOutWithMock(api, 'server_get')
self.mox.StubOutWithMock(api, 'snapshot_create')
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.snapshot_create(IsA(http.HttpRequest), server.id, snapshot.name) \
.AndRaise(self.exceptions.nova)
self.mox.ReplayAll()

View File

@ -24,7 +24,7 @@ Views for managing Nova instance snapshots.
import logging
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import api
@ -39,24 +39,24 @@ LOG = logging.getLogger(__name__)
class CreateView(forms.ModalFormView):
form_class = CreateSnapshot
template_name = 'nova/images_and_snapshots/snapshots/create.html'
success_url = reverse_lazy("horizon:nova:images_and_snapshots:index")
def get_object(self):
if not hasattr(self, "_object"):
try:
self._object = api.server_get(self.request,
self.kwargs["instance_id"])
except:
redirect = reverse('horizon:nova:instances:index')
exceptions.handle(self.request,
_("Unable to retrieve instance."),
redirect=redirect)
return self._object
def get_initial(self):
redirect = reverse('horizon:nova:instances:index')
instance_id = self.kwargs["instance_id"]
try:
self.instance = api.server_get(self.request, instance_id)
except:
self.instance = None
msg = _("Unable to retrieve instance.")
exceptions.handle(self.request, msg, redirect)
if self.instance.status != api.nova.INSTANCE_ACTIVE_STATE:
msg = _('To create a snapshot, the instance must be in '
'the "%s" state.') % api.nova.INSTANCE_ACTIVE_STATE
raise exceptions.Http302(redirect, message=msg)
return {"instance_id": instance_id,
"tenant_id": self.request.user.tenant_id}
return {"instance_id": self.kwargs["instance_id"]}
def get_context_data(self, **kwargs):
context = super(CreateView, self).get_context_data(**kwargs)
context['instance'] = self.instance
context['instance'] = self.get_object()
return context

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create An Image" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Create An Image") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'nova/images_and_snapshots/images/_create.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Image Detail "%}{% endblock %}
@ -7,7 +7,7 @@
{% include "horizon/common/_page_header.html" with title="Image Detail" %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
<div class="row-fluid">
<div class="span12">
{{ tab_group.render }}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Image" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Update Image") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'nova/images_and_snapshots/images/_update.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Images &amp; Snapshots" %}{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title=_("Images &amp; Snapshots") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
<div class="images">
{{ images_table.render }}
</div>

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Snapshot" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Create a Snapshot") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'nova/images_and_snapshots/snapshots/_create.html' %}
{% endblock %}

View File

@ -20,13 +20,13 @@
import logging
from django import shortcuts
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import exceptions
from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__)
@ -39,10 +39,12 @@ class UpdateInstance(forms.SelfHandlingForm):
def handle(self, request, data):
try:
api.server_update(request, data['instance'], data['name'])
server = api.server_update(request, data['instance'], data['name'])
messages.success(request,
_('Instance "%s" updated.') % data['name'])
return server
except:
exceptions.handle(request, _('Unable to update instance.'))
return shortcuts.redirect('horizon:nova:instances:index')
redirect = reverse("horizon:nova:instances:index")
exceptions.handle(request,
_('Unable to update instance.'),
redirect=redirect)

View File

@ -2,7 +2,7 @@
{% load i18n %}
{% block form_id %}update_instance_form{% endblock %}
{% block form_action %}{% url horizon:nova:instances:update instance.id %}{% endblock %}
{% block form_action %}{% url horizon:nova:instances:update instance_id %}{% endblock %}
{% block modal-header %}{% trans "Edit Instance" %}{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n sizeformat %}
{% block title %}{% trans "Instance Detail" %}{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title="Instance Detail: "|add:instance.name %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
<div class="row-fluid">
<div class="span12">
{{ tab_group.render }}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Instances" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Instances") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Launch Instance" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Launch Instance") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'horizon/common/_workflow.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Instance" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Update Instance") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'nova/instances/_update.html' %}
{% endblock %}

View File

@ -491,28 +491,22 @@ class InstanceTests(test.TestCase):
'server_delete',)})
def test_create_instance_snapshot(self):
server = self.servers.first()
snapshot_server = deepcopy(server)
setattr(snapshot_server, 'OS-EXT-STS:task_state',
"IMAGE_SNAPSHOT")
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.snapshot_create(IsA(http.HttpRequest),
server.id,
"snapshot1")
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
"snapshot1").AndReturn(self.snapshots.first())
api.snapshot_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([[], False])
marker=None).AndReturn([[], False])
api.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([[], False])
api.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
api.server_list(IsA(http.HttpRequest)).AndReturn([snapshot_server])
api.flavor_list(IgnoreArg()).AndReturn(self.flavors.list())
self.mox.ReplayAll()
formData = {'instance_id': server.id,
'method': 'CreateSnapshot',
'tenant_id': server.tenant_id,
'name': 'snapshot1'}
url = reverse('horizon:nova:images_and_snapshots:snapshots:create',
args=[server.id])
@ -520,10 +514,6 @@ class InstanceTests(test.TestCase):
res = self.client.post(url, formData)
self.assertRedirects(res, redir_url)
res = self.client.get(INDEX_URL)
self.assertContains(res, '<td class="status_unknown sortable">'
'Snapshotting</td>', 1)
@test.create_stubs({api: ('server_get',)})
def test_instance_update_get(self):
server = self.servers.first()
@ -532,12 +522,10 @@ class InstanceTests(test.TestCase):
self.mox.ReplayAll()
url = reverse('horizon:nova:instances:update',
args=[server.id])
url = reverse('horizon:nova:instances:update', args=[server.id])
res = self.client.get(url)
self.assertTemplateUsed(res,
'nova/instances/update.html')
self.assertTemplateUsed(res, 'nova/instances/update.html')
@test.create_stubs({api: ('server_get',)})
def test_instance_update_get_server_get_exception(self):
@ -559,7 +547,9 @@ class InstanceTests(test.TestCase):
server = self.servers.first()
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.server_update(IsA(http.HttpRequest), server.id, server.name)
api.server_update(IsA(http.HttpRequest),
server.id,
server.name).AndReturn(server)
self.mox.ReplayAll()

View File

@ -25,7 +25,7 @@ import logging
from django import http
from django import shortcuts
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.datastructures import SortedDict
from django.utils.translation import ugettext_lazy as _
@ -126,22 +126,28 @@ class UpdateView(forms.ModalFormView):
form_class = UpdateInstance
template_name = 'nova/instances/update.html'
context_object_name = 'instance'
success_url = reverse_lazy("horizon:nova:instances:index")
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
context["instance_id"] = self.kwargs['instance_id']
return context
def get_object(self, *args, **kwargs):
if not hasattr(self, "object"):
if not hasattr(self, "_object"):
instance_id = self.kwargs['instance_id']
try:
self.object = api.server_get(self.request, instance_id)
self._object = api.server_get(self.request, instance_id)
except:
redirect = reverse("horizon:nova:instances:index")
msg = _('Unable to retrieve instance details.')
exceptions.handle(self.request, msg, redirect=redirect)
return self.object
return self._object
def get_initial(self):
return {'instance': self.kwargs['instance_id'],
'tenant_id': self.request.user.tenant_id,
'name': getattr(self.object, 'name', '')}
'name': getattr(self.get_object(), 'name', '')}
class DetailView(tabs.TabView):

View File

@ -18,14 +18,14 @@
# License for the specific language governing permissions and limitations
# under the License.
from django import forms
from django.utils.text import normalize_newlines
from django.utils.translation import ugettext as _
from horizon import api
from horizon import exceptions
from horizon.openstack.common import jsonutils
from horizon import forms
from horizon import workflows
from horizon.openstack.common import jsonutils
class SelectProjectUserAction(workflows.Action):
@ -320,11 +320,15 @@ class SetInstanceDetails(workflows.Step):
return context
KEYPAIR_IMPORT_URL = "horizon:nova:access_and_security:keypairs:import"
class SetAccessControlsAction(workflows.Action):
keypair = forms.ChoiceField(label=_("Keypair"),
required=False,
help_text=_("Which keypair to use for "
"authentication."))
keypair = forms.DynamicChoiceField(label=_("Keypair"),
required=False,
help_text=_("Which keypair to use for "
"authentication."),
add_item_link=KEYPAIR_IMPORT_URL)
groups = forms.MultipleChoiceField(label=_("Security Groups"),
required=True,
initial=["default"],

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Instance Overview{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title=_("Overview") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include "horizon/common/_usage_summary.html" %}
{{ table.render }}
{% endblock %}

View File

@ -15,12 +15,3 @@
{% endif %}
{{ block.super }}
{% endblock %}
{% block sidebar %}
{% include 'horizon/common/_sidebar.html' %}
{% endblock %}
{% block main %}
{% include "horizon/_messages.html" %}
{% block dash_main %}{% endblock %}
{% endblock %}

View File

@ -7,14 +7,14 @@
Views for managing Nova volumes.
"""
from django import shortcuts
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import forms
from horizon import exceptions
from horizon import messages
from ..instances.tables import ACTIVE_STATES
@ -48,19 +48,19 @@ class CreateForm(forms.SelfHandlingForm):
' volumes.')
raise ValidationError(error_message)
api.volume_create(request, data['size'], data['name'],
data['description'])
volume = api.volume_create(request,
data['size'],
data['name'],
data['description'])
message = 'Creating volume "%s"' % data['name']
messages.info(request, message)
return volume
except ValidationError, e:
return self.api_error(e.messages[0])
except:
exceptions.handle(request, ignore=True)
return self.api_error(_("Unable to create volume."))
return shortcuts.redirect("horizon:nova:volumes:index")
class AttachForm(forms.SelfHandlingForm):
instance = forms.ChoiceField(label="Attach to Instance",
@ -113,10 +113,12 @@ class AttachForm(forms.SelfHandlingForm):
"inst": instance_name,
"dev": data['device']}
messages.info(request, message)
return True
except:
redirect = reverse("horizon:nova:volumes:index")
exceptions.handle(request,
_('Unable to attach volume.'))
return shortcuts.redirect("horizon:nova:volumes:index")
_('Unable to attach volume.'),
redirect=redirect)
class CreateSnapshotForm(forms.SelfHandlingForm):
@ -134,15 +136,16 @@ class CreateSnapshotForm(forms.SelfHandlingForm):
def handle(self, request, data):
try:
api.volume_snapshot_create(request,
data['volume_id'],
data['name'],
data['description'])
snapshot = api.volume_snapshot_create(request,
data['volume_id'],
data['name'],
data['description'])
message = _('Creating volume snapshot "%s"') % data['name']
messages.info(request, message)
return snapshot
except:
redirect = reverse("horizon:nova:images_and_snapshots:index")
exceptions.handle(request,
_('Unable to create volume snapshot.'))
return shortcuts.redirect("horizon:nova:images_and_snapshots:index")
_('Unable to create volume snapshot.'),
redirect=redirect)

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Manage Volume Attachments{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Manage Volume Attachments") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'nova/volumes/_attach.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Create Volume{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Create a Volume") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'nova/volumes/_create.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Volume Snapshot" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Create a Volume Snapshot") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{% include 'nova/volumes/_create_snapshot.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Volume Details" %}{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title=_("Volume Detail") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
<div class="row-fluid">
<div class="span12">
{{ tab_group.render }}

View File

@ -1,4 +1,4 @@
{% extends 'nova/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Volumes" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Volumes") %}
{% endblock page_header %}
{% block dash_main %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -29,6 +29,7 @@ from horizon import test
class VolumeViewTests(test.TestCase):
@test.create_stubs({api: ('tenant_quota_usages', 'volume_create',)})
def test_create_volume(self):
volume = self.volumes.first()
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
formData = {'name': u'A Volume I Am Making',
'description': u'This is a volume I am making for a test.',
@ -39,7 +40,7 @@ class VolumeViewTests(test.TestCase):
api.volume_create(IsA(http.HttpRequest),
formData['size'],
formData['name'],
formData['description'])
formData['description']).AndReturn(volume)
self.mox.ReplayAll()

View File

@ -20,6 +20,8 @@ Views for managing Nova volumes.
import logging
from django import shortcuts
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.utils.datastructures import SortedDict
@ -77,6 +79,7 @@ class DetailView(tabs.TabView):
class CreateView(forms.ModalFormView):
form_class = CreateForm
template_name = 'nova/volumes/create.html'
success_url = reverse_lazy("horizon:nova:volumes:index")
def get_context_data(self, **kwargs):
context = super(CreateView, self).get_context_data(**kwargs)
@ -84,24 +87,26 @@ class CreateView(forms.ModalFormView):
context['usages'] = api.tenant_quota_usages(self.request)
except:
exceptions.handle(self.request)
return context
class CreateSnapshotView(forms.ModalFormView):
form_class = CreateSnapshotForm
template_name = 'nova/volumes/create_snapshot.html'
success_url = reverse_lazy("horizon:nova:images_and_snapshots:index")
def get_context_data(self, **kwargs):
return {'volume_id': kwargs['volume_id']}
return {'volume_id': self.kwargs['volume_id']}
def get_initial(self):
return {'volume_id': self.kwargs["volume_id"]}
class EditAttachmentsView(tables.DataTableView):
class EditAttachmentsView(tables.DataTableView, forms.ModalFormView):
table_class = AttachmentsTable
form_class = AttachForm
template_name = 'nova/volumes/attach.html'
success_url = reverse_lazy("horizon:nova:volumes:index")
def get_object(self):
if not hasattr(self, "_object"):
@ -124,35 +129,40 @@ class EditAttachmentsView(tables.DataTableView):
_('Unable to retrieve volume information.'))
return attachments
def get_initial(self):
try:
instances = api.nova.server_list(self.request)
except:
instances = []
exceptions.handle(self.request,
_("Unable to retrieve attachment information."))
return {'volume': self.get_object(),
'instances': instances}
def get_form(self):
if not hasattr(self, "_form"):
form_class = self.get_form_class()
self._form = super(EditAttachmentsView, self).get_form(form_class)
return self._form
def get_context_data(self, **kwargs):
context = super(EditAttachmentsView, self).get_context_data(**kwargs)
context['form'] = self.form
context['form'] = self.get_form()
context['volume'] = self.get_object()
if self.request.is_ajax():
context['hide'] = True
return context
def handle_form(self):
instances = api.nova.server_list(self.request)
initial = {'volume': self.get_object(),
'instances': instances}
return AttachForm.maybe_handle(self.request, initial=initial)
def get(self, request, *args, **kwargs):
self.form, handled = self.handle_form()
if handled:
return handled
# Table action handling
handled = self.construct_tables()
if handled:
return handled
context = self.get_context_data(**kwargs)
context['form'] = self.form
if request.is_ajax():
context['hide'] = True
self.template_name = ('nova/volumes'
'/_attach.html')
return self.render_to_response(context)
return self.render_to_response(self.get_context_data(**kwargs))
def post(self, request, *args, **kwargs):
form, handled = self.handle_form()
if handled:
return handled
return super(EditAttachmentsView, self).post(request, *args, **kwargs)
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.get(request, *args, **kwargs)

View File

@ -34,14 +34,8 @@ LOG = logging.getLogger(__name__)
class DownloadX509Credentials(forms.SelfHandlingForm):
tenant = forms.ChoiceField(label=_("Select a Project"))
# forms.SelfHandlingForm doesn't pass request object as the first argument
# to the class __init__ method, which causes form to explode.
@classmethod
def _instantiate(cls, request, *args, **kwargs):
return cls(request, *args, **kwargs)
def __init__(self, request, *args, **kwargs):
super(DownloadX509Credentials, self).__init__(*args, **kwargs)
super(DownloadX509Credentials, self).__init__(request, *args, **kwargs)
# Populate tenant choices
tenant_choices = []
try:

View File

@ -1,4 +1,4 @@
{% extends 'settings/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Download EC2 Credentials" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Download EC2 Credentials") %}
{% endblock page_header %}
{% block settings_main %}
{% block main %}
{% include "settings/ec2/download_form.html" %}
{% endblock %}

View File

@ -26,3 +26,6 @@ LOG = logging.getLogger(__name__)
class IndexView(forms.ModalFormView):
form_class = DownloadX509Credentials
template_name = 'settings/ec2/index.html'
def form_valid(self, form):
return form.handle(self.request, form.cleaned_data)

View File

@ -21,11 +21,11 @@
import logging
from django import shortcuts
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__)
@ -34,14 +34,8 @@ LOG = logging.getLogger(__name__)
class DownloadOpenRCForm(forms.SelfHandlingForm):
tenant = forms.ChoiceField(label=_("Select a Project"))
# forms.SelfHandlingForm doesn't pass request object as the first argument
# to the class __init__ method, which causes form to explode.
@classmethod
def _instantiate(cls, request, *args, **kwargs):
return cls(request, *args, **kwargs)
def __init__(self, request, *args, **kwargs):
super(DownloadOpenRCForm, self).__init__(*args, **kwargs)
super(DownloadOpenRCForm, self).__init__(request, *args, **kwargs)
# Populate tenant choices
tenant_choices = []
for tenant in api.tenant_list(request):

View File

@ -1,4 +1,4 @@
{% extends 'settings/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "OpenStack API" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("OpenStack API") %}
{% endblock page_header %}
{% block settings_main %}
{% block main %}
{% include "settings/project/_openrc.html" %}
{% endblock %}

View File

@ -39,3 +39,6 @@ class OpenRCView(ModalFormView):
def get_initial(self):
return {'tenant': self.request.user.tenant_id}
def form_valid(self, form):
return form.handle(self.request, form.cleaned_data)

View File

@ -1,10 +0,0 @@
{% extends 'base.html' %}
{% block sidebar %}
{% include 'horizon/common/_sidebar.html' %}
{% endblock %}
{% block main %}
{% include "horizon/_messages.html" %}
{% block settings_main %}{% endblock %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'settings/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "User Settings" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("User Settings") %}
{% endblock page_header %}
{% block settings_main %}
{% block main %}
{% include "settings/user/_settings.html" %}
{% endblock %}

View File

@ -26,3 +26,6 @@ class UserSettingsView(forms.ModalFormView):
def get_initial(self):
return {'language': self.request.LANGUAGE_CODE,
'timezone': self.request.session.get('django_timezone', 'UTC')}
def form_valid(self, form):
return form.handle(self.request, form.cleaned_data)

View File

@ -20,12 +20,12 @@
import logging
from django import shortcuts
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import exceptions
from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__)
@ -41,14 +41,16 @@ class CreateFlavor(forms.SelfHandlingForm):
eph_gb = forms.IntegerField(label=_("Ephemeral Disk GB"))
def handle(self, request, data):
api.flavor_create(request,
data['name'],
data['memory_mb'],
data['vcpus'],
data['disk_gb'],
data['flavor_id'],
ephemeral=data['eph_gb'])
msg = _('%s was successfully added to flavors.') % data['name']
LOG.info(msg)
messages.success(request, msg)
return shortcuts.redirect('horizon:syspanel:flavors:index')
try:
flavor = api.flavor_create(request,
data['name'],
data['memory_mb'],
data['vcpus'],
data['disk_gb'],
data['flavor_id'],
ephemeral=data['eph_gb'])
msg = _('%s was successfully added to flavors.') % data['name']
messages.success(request, msg)
return flavor
except:
exceptions.handle(request, _("Unable to create flavor"))

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Create Flavors{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Create Flavor") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{% include "syspanel/flavors/_create.html" %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Flavors{% endblock %}
@ -8,6 +8,6 @@
{% include "horizon/common/_page_header.html" with title=_("Flavors") refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -20,11 +20,11 @@
import logging
from django.contrib import messages
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from novaclient import exceptions as api_exceptions
from horizon import api
from horizon import exceptions
from horizon import forms
from horizon import tables
from .forms import CreateFlavor
@ -43,15 +43,9 @@ class IndexView(tables.DataTableView):
flavors = []
try:
flavors = api.flavor_list(request)
except api_exceptions.Unauthorized, e:
LOG.exception('Unauthorized attempt to access flavor list.')
messages.error(request, _('Unauthorized.'))
except Exception, e:
LOG.exception('Exception while fetching usage info')
if not hasattr(e, 'message'):
e.message = str(e)
messages.error(request, _('Unable to get flavor list: %s') %
e.message)
except:
exceptions.handle(request,
_('Unable to retrieve flavor list.'))
flavors.sort(key=lambda x: x.id, reverse=True)
return flavors
@ -59,11 +53,15 @@ class IndexView(tables.DataTableView):
class CreateView(forms.ModalFormView):
form_class = CreateFlavor
template_name = 'syspanel/flavors/create.html'
success_url = reverse_lazy('horizon:syspanel:flavors:index')
def get_initial(self):
# TODO(tres): Get rid of this hacky bit of nonsense after flavors get
# converted to nova client.
flavors = api.flavor_list(self.request)
# TODO(tres): Get rid of this hacky bit of nonsense after flavors
# id handling gets fixed.
try:
flavors = api.flavor_list(self.request)
except:
exceptions.handle(self.request, ignore=True)
if flavors:
largest_id = max(flavors, key=lambda f: f.id).id
return {'flavor_id': int(largest_id) + 1}

View File

@ -18,13 +18,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
from horizon.dashboards.nova.images_and_snapshots.images import forms
LOG = logging.getLogger(__name__)
class AdminUpdateImageForm(forms.UpdateImageForm):
completion_view = 'horizon:syspanel:images:index'
pass

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Images" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Images") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Image" %}{% endblock %}
@ -7,6 +7,6 @@
{% include "horizon/common/_page_header.html" with title=_("Update Image") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{% include 'syspanel/images/_update.html' %}
{% endblock %}

View File

@ -20,6 +20,7 @@
import logging
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import api
@ -57,6 +58,7 @@ class IndexView(tables.DataTableView):
class UpdateView(views.UpdateView):
template_name = 'syspanel/images/update.html'
form_class = AdminUpdateImageForm
success_url = reverse_lazy('horizon:syspanel:images:index')
class DetailView(views.DetailView):

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Instances" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("All Instances") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n sizeformat %}
{% block title %}{% trans "Usage Overview" %}{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title=_("Overview") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{% if monitoring %}
<div id="monitoring">
<h3>{% trans "Monitoring" %}: </h3>

View File

@ -20,13 +20,12 @@
import logging
from django import shortcuts
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import exceptions
from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__)
@ -50,10 +49,9 @@ class AddUser(forms.SelfHandlingForm):
data['user_id'],
data['role_id'])
messages.success(request, _('Successfully added user to project.'))
return True
except:
exceptions.handle(request, _('Unable to add user to project.'))
return shortcuts.redirect('horizon:syspanel:projects:users',
tenant_id=data['tenant_id'])
class CreateTenant(forms.SelfHandlingForm):
@ -68,16 +66,16 @@ class CreateTenant(forms.SelfHandlingForm):
def handle(self, request, data):
try:
LOG.info('Creating project with name "%s"' % data['name'])
api.tenant_create(request,
data['name'],
data['description'],
data['enabled'])
project = api.tenant_create(request,
data['name'],
data['description'],
data['enabled'])
messages.success(request,
_('%s was successfully created.')
% data['name'])
return project
except:
exceptions.handle(request, _('Unable to create project.'))
return shortcuts.redirect('horizon:syspanel:projects:index')
class UpdateTenant(forms.SelfHandlingForm):
@ -92,17 +90,17 @@ class UpdateTenant(forms.SelfHandlingForm):
def handle(self, request, data):
try:
LOG.info('Updating project with id "%s"' % data['id'])
api.tenant_update(request,
data['id'],
data['name'],
data['description'],
data['enabled'])
project = api.tenant_update(request,
data['id'],
data['name'],
data['description'],
data['enabled'])
messages.success(request,
_('%s was successfully updated.')
% data['name'])
return project
except:
exceptions.handle(request, _('Unable to update project.'))
return shortcuts.redirect('horizon:syspanel:projects:index')
class UpdateQuotas(forms.SelfHandlingForm):
@ -136,6 +134,6 @@ class UpdateQuotas(forms.SelfHandlingForm):
messages.success(request,
_('Quotas for %s were successfully updated.')
% data['tenant_id'])
return True
except:
exceptions.handle(request, _('Unable to update quotas.'))
return shortcuts.redirect('horizon:syspanel:projects:index')

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Add User To Project" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Add User To Project") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{% include 'syspanel/projects/_add_user.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Create Project{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Create Project") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{% include 'syspanel/projects/_create.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Projects{% endblock %}
@ -8,6 +8,6 @@
{% include "horizon/common/_page_header.html" with title=_("Projects") refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Modify Project Quotas{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Update Project") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{% include 'syspanel/projects/_quotas.html' with form=form %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Update Project{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Update Project") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{% include 'syspanel/projects/_update.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n sizeformat %}
{% block title %}{% trans "Project Usage Overview" %}{% endblock %}
@ -8,7 +8,7 @@
</div>
{% endblock %}
{% block syspanel_main %}
{% block main %}
{% include "horizon/common/_usage_summary.html" %}
{{ table.render }}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Project Users{% endblock %}
@ -8,7 +8,7 @@
</div>
{% endblock %}
{% block syspanel_main %}
{% block main %}
<div id="tenant_users_table">
{{ tenant_users_table.render }}
</div>

View File

@ -51,8 +51,6 @@ class TenantsViewTests(test.BaseAdminViewTests):
self.mox.StubOutWithMock(api.keystone, 'tenant_get')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_get')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_update')
api.keystone.tenant_get(IgnoreArg(), tenant.id, admin=True) \
.AndReturn(tenant)
api.nova.tenant_quota_get(IgnoreArg(), tenant.id).AndReturn(quota)
api.nova.tenant_quota_update(IgnoreArg(), tenant.id, **quota_data)
self.mox.ReplayAll()

View File

@ -21,7 +21,7 @@
import logging
import operator
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import api
@ -36,6 +36,27 @@ from .tables import TenantsTable, TenantUsersTable, AddUsersTable
LOG = logging.getLogger(__name__)
class TenantContextMixin(object):
def get_object(self):
if not hasattr(self, "_object"):
tenant_id = self.kwargs['tenant_id']
try:
self._object = api.keystone.tenant_get(self.request,
tenant_id,
admin=True)
except:
redirect = reverse("horizon:syspanel:projects:index")
exceptions.handle(self.request,
_('Unable to retrieve project information.'),
redirect=redirect)
return self._object
def get_context_data(self, **kwargs):
context = super(TenantContextMixin, self).get_context_data(**kwargs)
context['tenant'] = self.get_object()
return context
class IndexView(tables.DataTableView):
table_class = TenantsTable
template_name = 'syspanel/projects/index.html'
@ -54,28 +75,20 @@ class IndexView(tables.DataTableView):
class CreateView(forms.ModalFormView):
form_class = CreateTenant
template_name = 'syspanel/projects/create.html'
success_url = reverse_lazy('horizon:syspanel:projects:index')
class UpdateView(forms.ModalFormView):
class UpdateView(TenantContextMixin, forms.ModalFormView):
form_class = UpdateTenant
template_name = 'syspanel/projects/update.html'
context_object_name = 'tenant'
def get_object(self, *args, **kwargs):
tenant_id = kwargs['tenant_id']
try:
return api.keystone.tenant_get(self.request, tenant_id, admin=True)
except:
redirect = reverse("horizon:syspanel:projects:index")
exceptions.handle(self.request,
_('Unable to retrieve project.'),
redirect=redirect)
success_url = reverse_lazy('horizon:syspanel:projects:index')
def get_initial(self):
return {'id': self.object.id,
'name': self.object.name,
'description': getattr(self.object, "description", ""),
'enabled': self.object.enabled}
project = self.get_object()
return {'id': project.id,
'name': project.name,
'description': project.description,
'enabled': project.enabled}
class UsersView(tables.MultiTableView):
@ -116,15 +129,14 @@ class UsersView(tables.MultiTableView):
return context
class AddUserView(forms.ModalFormView):
class AddUserView(TenantContextMixin, forms.ModalFormView):
form_class = AddUser
template_name = 'syspanel/projects/add_user.html'
context_object_name = 'tenant'
success_url = 'horizon:syspanel:projects:users'
def get_object(self, *args, **kwargs):
return api.keystone.tenant_get(self.request,
kwargs["tenant_id"],
admin=True)
def get_success_url(self):
return reverse(self.success_url,
args=(self.request.POST['tenant_id'],))
def get_context_data(self, **kwargs):
context = super(AddUserView, self).get_context_data(**kwargs)
@ -153,19 +165,19 @@ class AddUserView(forms.ModalFormView):
'role_id': getattr(default_role, "id", None)}
class QuotasView(forms.ModalFormView):
class QuotasView(TenantContextMixin, forms.ModalFormView):
form_class = UpdateQuotas
template_name = 'syspanel/projects/quotas.html'
context_object_name = 'tenant'
def get_object(self, *args, **kwargs):
return api.keystone.tenant_get(self.request,
kwargs["tenant_id"],
admin=True)
success_url = reverse_lazy('horizon:syspanel:projects:index')
def get_initial(self):
quotas = api.nova.tenant_quota_get(self.request,
self.kwargs['tenant_id'])
try:
quotas = api.nova.tenant_quota_get(self.request,
self.kwargs['tenant_id'])
except:
exceptions.handle(self.request,
_("Unable to retrieve quota information."),
redirect=reverse(self.get_sucess_url))
return {
'tenant_id': self.kwargs['tenant_id'],
'metadata_items': quotas.metadata_items,

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Quotas{% endblock %}
@ -8,6 +8,6 @@
{% include "horizon/common/_page_header.html" with title=_("Default Quotas") refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Services{% endblock %}
@ -8,7 +8,7 @@
{% include "horizon/common/_page_header.html" with title=_("Services") refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -1,10 +0,0 @@
{% extends 'base.html' %}
{% block sidebar %}
{% include 'horizon/common/_sidebar.html' %}
{% endblock %}
{% block main %}
{% include "horizon/_messages.html" %}
{% block syspanel_main %}{% endblock %}
{% endblock %}

View File

@ -20,8 +20,6 @@
import logging
from django import shortcuts
from django.contrib import messages
from django.forms import ValidationError
from django.utils.translation import force_unicode, ugettext_lazy as _
from django.views.decorators.debug import sensitive_variables
@ -29,6 +27,7 @@ from django.views.decorators.debug import sensitive_variables
from horizon import api
from horizon import exceptions
from horizon import forms
from horizon import messages
from horizon.utils import validators
@ -37,7 +36,7 @@ LOG = logging.getLogger(__name__)
class BaseUserForm(forms.SelfHandlingForm):
def __init__(self, request, *args, **kwargs):
super(BaseUserForm, self).__init__(*args, **kwargs)
super(BaseUserForm, self).__init__(request, *args, **kwargs)
# Populate tenant choices
tenant_choices = [('', _("Select a project"))]
@ -46,10 +45,6 @@ class BaseUserForm(forms.SelfHandlingForm):
tenant_choices.append((tenant.id, tenant.name))
self.fields['tenant_id'].choices = tenant_choices
@classmethod
def _instantiate(cls, request, *args, **kwargs):
return cls(request, *args, **kwargs)
def clean(self):
'''Check to make sure password fields match.'''
data = super(forms.Form, self).clean()
@ -103,10 +98,9 @@ class CreateUserForm(BaseUserForm):
except:
exceptions.handle(request,
_('Unable to add user to primary project.'))
return shortcuts.redirect('horizon:syspanel:users:index')
return new_user
except:
exceptions.handle(request, _('Unable to create user.'))
return shortcuts.redirect('horizon:syspanel:users:index')
class UpdateUserForm(BaseUserForm):
@ -140,7 +134,6 @@ class UpdateUserForm(BaseUserForm):
user_is_editable = api.keystone_can_edit_user()
user = data.pop('id')
tenant = data.pop('tenant_id')
data.pop('method')
if user_is_editable:
password = data.pop('password')
@ -184,4 +177,4 @@ class UpdateUserForm(BaseUserForm):
messages.error(request,
_('Unable to update %(attributes)s for the user.')
% {"attributes": ", ".join(failed)})
return shortcuts.redirect('horizon:syspanel:users:index')
return True

View File

@ -1,9 +1,9 @@
import logging
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from horizon import api
from horizon import messages
from horizon import tables

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Create User{% endblock %}
@ -7,6 +7,6 @@
{% include "horizon/common/_page_header.html" with title=_("Create User") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{% include 'syspanel/users/_create.html' %}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Users{% endblock %}
@ -8,6 +8,6 @@
{% include "horizon/common/_page_header.html" with title=_("Users") refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}Update User{% endblock %}
@ -7,6 +7,6 @@
{% include "horizon/common/_page_header.html" with title=_("Update User") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{% include 'syspanel/users/_update.html' %}
{% endblock %}

View File

@ -20,7 +20,7 @@
import operator
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.debug import sensitive_post_parameters
@ -50,33 +50,43 @@ class IndexView(tables.DataTableView):
class UpdateView(forms.ModalFormView):
form_class = UpdateUserForm
template_name = 'syspanel/users/update.html'
context_object_name = 'user'
success_url = reverse_lazy('horizon:syspanel:users:index')
@method_decorator(sensitive_post_parameters('password',
'confirm_password'))
def dispatch(self, *args, **kwargs):
return super(UpdateView, self).dispatch(*args, **kwargs)
def get_object(self, *args, **kwargs):
user_id = kwargs['user_id']
try:
return api.user_get(self.request, user_id, admin=True)
except:
redirect = reverse("horizon:syspanel:users:index")
exceptions.handle(self.request,
_('Unable to update user.'),
redirect=redirect)
def get_object(self):
if not hasattr(self, "_object"):
try:
self._object = api.user_get(self.request,
self.kwargs['user_id'],
admin=True)
except:
redirect = reverse("horizon:syspanel:users:index")
exceptions.handle(self.request,
_('Unable to update user.'),
redirect=redirect)
return self._object
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
context['user'] = self.get_object()
return context
def get_initial(self):
return {'id': self.object.id,
'name': getattr(self.object, 'name', None),
'tenant_id': getattr(self.object, 'tenantId', None),
'email': getattr(self.object, 'email', '')}
user = self.get_object()
return {'id': user.id,
'name': user.name,
'tenant_id': getattr(user, 'tenantId', None),
'email': user.email}
class CreateView(forms.ModalFormView):
form_class = CreateUserForm
template_name = 'syspanel/users/create.html'
success_url = reverse_lazy('horizon:syspanel:users:index')
@method_decorator(sensitive_post_parameters('password',
'confirm_password'))

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Volume Details" %}{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title=_("Volume Detail") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
<div class="row-fluid">
<div class="span12">
{{ tab_group.render }}

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %}
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Volumes" %}{% endblock %}
@ -6,7 +6,7 @@
{% include "horizon/common/_page_header.html" with title=_("Volumes") %}
{% endblock page_header %}
{% block syspanel_main %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -22,3 +22,4 @@ from django.forms import widgets
# Convenience imports for public API components.
from .base import SelfHandlingForm, DateForm
from .views import ModalFormView
from .fields import DynamicTypedChoiceField, DynamicChoiceField

View File

@ -18,41 +18,22 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
from django import forms
from django.forms.forms import NON_FIELD_ERRORS
from django.core.urlresolvers import reverse
from django.utils import dates, timezone
from horizon import exceptions
LOG = logging.getLogger(__name__)
class SelfHandlingForm(forms.Form):
"""
A base :class:`Form <django:django.forms.Form>` class which includes
processing logic in its subclasses and handling errors raised during
form processing.
.. attribute:: method
A :class:`CharField <django:django.forms.CharField>` instance
rendered with a
:class:`CharField <django:django.forms.widgets.HiddenInput>`
widget which is automatically set to the value of the class name.
This is used to determine whether this form should handle the
input it is given or not.
processing logic in its subclasses.
"""
method = forms.CharField(required=True, widget=forms.HiddenInput)
def __init__(self, *args, **kwargs):
initial = kwargs.pop('initial', {})
initial['method'] = self.__class__.__name__
kwargs['initial'] = initial
def __init__(self, request, *args, **kwargs):
self.request = request
if not hasattr(self, "handle"):
raise NotImplementedError("%s does not define a handle method."
% self.__class__.__name__)
super(SelfHandlingForm, self).__init__(*args, **kwargs)
def api_error(self, message):
@ -64,52 +45,6 @@ class SelfHandlingForm(forms.Form):
"""
self._errors[NON_FIELD_ERRORS] = self.error_class([message])
def get_success_url(self, request=None):
"""
Returns the URL to redirect to after a successful handling.
"""
if self.completion_view:
return reverse(self.completion_view)
if self.completion_url:
return self.completion_url
return request.get_full_path()
@classmethod
def _instantiate(cls, request, *args, **kwargs):
""" Instantiates the form. Allows customization in subclasses. """
return cls(*args, **kwargs)
@classmethod
def maybe_handle(cls, request, *args, **kwargs):
"""
If the form is valid,
:meth:`~horizon.forms.SelfHandlingForm.maybe_handle` calls a
``handle(request, data)`` method on its subclass to
determine what action to take.
Any exceptions raised during processing are captured and
converted to messages.
"""
if request.method != 'POST' or \
cls.__name__ != request.POST.get('method'):
return cls._instantiate(request, *args, **kwargs), None
if request.FILES:
form = cls._instantiate(request, request.POST, request.FILES,
*args, **kwargs)
else:
form = cls._instantiate(request, request.POST, *args, **kwargs)
if not form.is_valid():
return form, None
try:
return form, form.handle(request, form.cleaned_data)
except:
exceptions.handle(request)
return form, None
class DateForm(forms.Form):
""" A simple form for selecting a start date. """

Some files were not shown because too many files have changed in this diff Show More