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): 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): def server_add_floating_ip(request, server, floating_ip):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ Views for managing Nova keypairs.
import logging import logging
from django import http 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.template.defaultfilters import slugify
from django.views.generic import View, TemplateView from django.views.generic import View, TemplateView
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -41,11 +41,20 @@ LOG = logging.getLogger(__name__)
class CreateView(forms.ModalFormView): class CreateView(forms.ModalFormView):
form_class = CreateKeypair form_class = CreateKeypair
template_name = 'nova/access_and_security/keypairs/create.html' 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): class ImportView(forms.ModalFormView):
form_class = ImportKeypair form_class = ImportKeypair
template_name = 'nova/access_and_security/keypairs/import.html' 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): class DownloadView(TemplateView):

View File

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

View File

@ -100,30 +100,16 @@ class SecurityGroupsViewTests(test.TestCase):
def test_edit_rules_get_exception(self): def test_edit_rules_get_exception(self):
sec_group = self.security_groups.first() 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_get')
self.mox.StubOutWithMock(api, 'security_group_list') 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), api.security_group_get(IsA(http.HttpRequest),
sec_group.id).AndRaise(self.exceptions.nova) 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() self.mox.ReplayAll()
res = self.client.get(self.edit_url) 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): def test_edit_rules_add_rule_cidr(self):
sec_group = self.security_groups.first() sec_group = self.security_groups.first()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -93,7 +93,9 @@ class ContainerViewTests(test.TestCase):
'method': forms.CreateContainer.__name__} 'method': forms.CreateContainer.__name__}
res = self.client.post(reverse('horizon:nova:containers:create'), res = self.client.post(reverse('horizon:nova:containers:create'),
formData) 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): class ObjectViewTests(test.TestCase):

View File

@ -63,6 +63,17 @@ class IndexView(tables.DataTableView):
class CreateView(forms.ModalFormView): class CreateView(forms.ModalFormView):
form_class = CreateContainer form_class = CreateContainer
template_name = 'nova/containers/create.html' 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): def get_initial(self):
initial = super(CreateView, self).get_initial() initial = super(CreateView, self).get_initial()
@ -132,6 +143,12 @@ class ObjectIndexView(tables.MultiTableView):
class UploadView(forms.ModalFormView): class UploadView(forms.ModalFormView):
form_class = UploadObject form_class = UploadObject
template_name = 'nova/containers/upload.html' 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): def get_initial(self):
return {"container_name": self.kwargs["container_name"], return {"container_name": self.kwargs["container_name"],
@ -172,6 +189,12 @@ def object_download(request, container_name, object_path):
class CopyView(forms.ModalFormView): class CopyView(forms.ModalFormView):
form_class = CopyObject form_class = CopyObject
template_name = 'nova/containers/copy.html' 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): def get_form_kwargs(self):
kwargs = super(CopyView, self).get_form_kwargs() kwargs = super(CopyView, self).get_form_kwargs()

View File

@ -24,26 +24,23 @@ Views for managing Nova images.
import logging import logging
from django import shortcuts
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from horizon import api from horizon import api
from horizon import exceptions from horizon import exceptions
from horizon import forms from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class CreateImageForm(forms.SelfHandlingForm): class CreateImageForm(forms.SelfHandlingForm):
completion_view = 'horizon:nova:images_and_snapshots:index'
name = forms.CharField(max_length="255", label=_("Name"), required=True) name = forms.CharField(max_length="255", label=_("Name"), required=True)
copy_from = forms.CharField(max_length="255", copy_from = forms.CharField(max_length="255",
label=_("Image Location"), label=_("Image Location"),
help_text=_("An external (HTTP) URL where" help_text=_("An external (HTTP) URL to load "
" the image should be loaded from."), "the image from."),
required=True) required=True)
disk_format = forms.ChoiceField(label=_('Format'), disk_format = forms.ChoiceField(label=_('Format'),
required=True, required=True,
@ -103,19 +100,16 @@ class CreateImageForm(forms.SelfHandlingForm):
'name': data['name']} 'name': data['name']}
try: try:
api.glance.image_create(request, **meta) image = api.glance.image_create(request, **meta)
messages.success(request, messages.success(request,
_('Your image %s has been queued for creation.' % _('Your image %s has been queued for creation.' %
data['name'])) data['name']))
return image
except: except:
exceptions.handle(request, _('Unable to create new image.')) exceptions.handle(request, _('Unable to create new image.'))
return shortcuts.redirect(self.get_success_url())
class UpdateImageForm(forms.SelfHandlingForm): class UpdateImageForm(forms.SelfHandlingForm):
completion_view = 'horizon:nova:images_and_snapshots:index'
image_id = forms.CharField(widget=forms.HiddenInput()) image_id = forms.CharField(widget=forms.HiddenInput())
name = forms.CharField(max_length="255", label=_("Name")) name = forms.CharField(max_length="255", label=_("Name"))
kernel = forms.CharField(max_length="36", label=_("Kernel ID"), kernel = forms.CharField(max_length="36", label=_("Kernel ID"),
@ -139,13 +133,17 @@ class UpdateImageForm(forms.SelfHandlingForm):
public = forms.BooleanField(label=_("Public"), required=False) public = forms.BooleanField(label=_("Public"), required=False)
def handle(self, request, data): def handle(self, request, data):
# TODO add public flag to image meta properties
image_id = data['image_id'] image_id = data['image_id']
error_updating = _('Unable to update image "%s".') 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'], meta = {'is_public': data['public'],
'disk_format': data['disk_format'], 'disk_format': data['disk_format'],
'container_format': 'bare', 'container_format': container_format,
'name': data['name'], 'name': data['name'],
'properties': {}} 'properties': {}}
if data['kernel']: if data['kernel']:
@ -154,13 +152,13 @@ class UpdateImageForm(forms.SelfHandlingForm):
meta['properties']['ramdisk_id'] = data['ramdisk'] meta['properties']['ramdisk_id'] = data['ramdisk']
if data['architecture']: if data['architecture']:
meta['properties']['architecture'] = 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: try:
# Ensure we do not delete properties that have already been image = api.image_update(request, image_id, **meta)
# set on an image.
meta['features'] = {'X-Glance-Registry-Purge-Props': False}
api.image_update(request, image_id, **meta)
messages.success(request, _('Image was successfully updated.')) messages.success(request, _('Image was successfully updated.'))
return image
except: except:
exceptions.handle(request, error_updating % image_id) 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 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 django.utils.translation import ugettext_lazy as _
from horizon import api from horizon import api
@ -43,35 +43,39 @@ class CreateView(forms.ModalFormView):
form_class = CreateImageForm form_class = CreateImageForm
template_name = 'nova/images_and_snapshots/images/create.html' template_name = 'nova/images_and_snapshots/images/create.html'
context_object_name = 'image' context_object_name = 'image'
success_url = reverse_lazy("horizon:nova:images_and_snapshots:index")
class UpdateView(forms.ModalFormView): class UpdateView(forms.ModalFormView):
form_class = UpdateImageForm form_class = UpdateImageForm
template_name = 'nova/images_and_snapshots/images/update.html' 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): def get_object(self):
try: if not hasattr(self, "_object"):
self.object = api.image_get(self.request, kwargs['image_id']) try:
except: self._object = api.image_get(self.request,
msg = _('Unable to retrieve image.') self.kwargs['image_id'])
redirect = reverse('horizon:nova:images_and_snapshots:index') except:
exceptions.handle(self.request, msg, redirect=redirect) msg = _('Unable to retrieve image.')
return self.object 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): def get_initial(self):
properties = self.object.properties image = self.get_object()
# 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"
return {'image_id': self.kwargs['image_id'], return {'image_id': self.kwargs['image_id'],
'name': self.object.name, 'name': image.name,
'kernel': properties.get('kernel_id', ''), 'kernel': image.properties.get('kernel_id', ''),
'ramdisk': properties.get('ramdisk_id', ''), 'ramdisk': image.properties.get('ramdisk_id', ''),
'architecture': properties.get('architecture', ''), 'architecture': image.properties.get('architecture', ''),
'container_format': self.object.container_format, 'disk_format': image.disk_format,
'disk_format': self.object.disk_format, 'public': image.is_public == "True"}
'public': public}
class DetailView(tabs.TabView): class DetailView(tabs.TabView):

View File

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

View File

@ -42,18 +42,6 @@ class SnapshotsViewTests(test.TestCase):
self.assertTemplateUsed(res, self.assertTemplateUsed(res,
'nova/images_and_snapshots/snapshots/create.html') '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): def test_create_get_server_exception(self):
server = self.servers.first() server = self.servers.first()
self.mox.StubOutWithMock(api, 'server_get') 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.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.snapshot_create(IsA(http.HttpRequest), server.id, snapshot.name) \ api.snapshot_create(IsA(http.HttpRequest), server.id, snapshot.name) \
.AndReturn(snapshot) .AndReturn(snapshot)
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
self.mox.ReplayAll() self.mox.ReplayAll()
formData = {'method': 'CreateSnapshot', formData = {'method': 'CreateSnapshot',
@ -95,7 +82,6 @@ class SnapshotsViewTests(test.TestCase):
self.mox.StubOutWithMock(api, 'server_get') self.mox.StubOutWithMock(api, 'server_get')
self.mox.StubOutWithMock(api, 'snapshot_create') 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) \ api.snapshot_create(IsA(http.HttpRequest), server.id, snapshot.name) \
.AndRaise(self.exceptions.nova) .AndRaise(self.exceptions.nova)
self.mox.ReplayAll() self.mox.ReplayAll()

View File

@ -24,7 +24,7 @@ Views for managing Nova instance snapshots.
import logging 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 django.utils.translation import ugettext_lazy as _
from horizon import api from horizon import api
@ -39,24 +39,24 @@ LOG = logging.getLogger(__name__)
class CreateView(forms.ModalFormView): class CreateView(forms.ModalFormView):
form_class = CreateSnapshot form_class = CreateSnapshot
template_name = 'nova/images_and_snapshots/snapshots/create.html' 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): def get_initial(self):
redirect = reverse('horizon:nova:instances:index') return {"instance_id": self.kwargs["instance_id"]}
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}
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CreateView, self).get_context_data(**kwargs) context = super(CreateView, self).get_context_data(**kwargs)
context['instance'] = self.instance context['instance'] = self.get_object()
return context return context

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
{% load i18n %} {% load i18n %}
{% block form_id %}update_instance_form{% endblock %} {% 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 %} {% block modal-header %}{% trans "Edit Instance" %}{% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

@ -491,28 +491,22 @@ class InstanceTests(test.TestCase):
'server_delete',)}) 'server_delete',)})
def test_create_instance_snapshot(self): def test_create_instance_snapshot(self):
server = self.servers.first() 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.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.snapshot_create(IsA(http.HttpRequest), api.snapshot_create(IsA(http.HttpRequest),
server.id, server.id,
"snapshot1") "snapshot1").AndReturn(self.snapshots.first())
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
api.snapshot_list_detailed(IsA(http.HttpRequest), api.snapshot_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([[], False]) marker=None).AndReturn([[], False])
api.image_list_detailed(IsA(http.HttpRequest), api.image_list_detailed(IsA(http.HttpRequest),
marker=None).AndReturn([[], False]) marker=None).AndReturn([[], False])
api.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([]) 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() self.mox.ReplayAll()
formData = {'instance_id': server.id, formData = {'instance_id': server.id,
'method': 'CreateSnapshot', 'method': 'CreateSnapshot',
'tenant_id': server.tenant_id,
'name': 'snapshot1'} 'name': 'snapshot1'}
url = reverse('horizon:nova:images_and_snapshots:snapshots:create', url = reverse('horizon:nova:images_and_snapshots:snapshots:create',
args=[server.id]) args=[server.id])
@ -520,10 +514,6 @@ class InstanceTests(test.TestCase):
res = self.client.post(url, formData) res = self.client.post(url, formData)
self.assertRedirects(res, redir_url) 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',)}) @test.create_stubs({api: ('server_get',)})
def test_instance_update_get(self): def test_instance_update_get(self):
server = self.servers.first() server = self.servers.first()
@ -532,12 +522,10 @@ class InstanceTests(test.TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
url = reverse('horizon:nova:instances:update', url = reverse('horizon:nova:instances:update', args=[server.id])
args=[server.id])
res = self.client.get(url) res = self.client.get(url)
self.assertTemplateUsed(res, self.assertTemplateUsed(res, 'nova/instances/update.html')
'nova/instances/update.html')
@test.create_stubs({api: ('server_get',)}) @test.create_stubs({api: ('server_get',)})
def test_instance_update_get_server_get_exception(self): def test_instance_update_get_server_get_exception(self):
@ -559,7 +547,9 @@ class InstanceTests(test.TestCase):
server = self.servers.first() server = self.servers.first()
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server) 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() self.mox.ReplayAll()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,14 +34,8 @@ LOG = logging.getLogger(__name__)
class DownloadX509Credentials(forms.SelfHandlingForm): class DownloadX509Credentials(forms.SelfHandlingForm):
tenant = forms.ChoiceField(label=_("Select a Project")) 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): def __init__(self, request, *args, **kwargs):
super(DownloadX509Credentials, self).__init__(*args, **kwargs) super(DownloadX509Credentials, self).__init__(request, *args, **kwargs)
# Populate tenant choices # Populate tenant choices
tenant_choices = [] tenant_choices = []
try: try:

View File

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

View File

@ -26,3 +26,6 @@ LOG = logging.getLogger(__name__)
class IndexView(forms.ModalFormView): class IndexView(forms.ModalFormView):
form_class = DownloadX509Credentials form_class = DownloadX509Credentials
template_name = 'settings/ec2/index.html' 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 import logging
from django import shortcuts from django import shortcuts
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from horizon import api from horizon import api
from horizon import forms from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -34,14 +34,8 @@ LOG = logging.getLogger(__name__)
class DownloadOpenRCForm(forms.SelfHandlingForm): class DownloadOpenRCForm(forms.SelfHandlingForm):
tenant = forms.ChoiceField(label=_("Select a Project")) 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): def __init__(self, request, *args, **kwargs):
super(DownloadOpenRCForm, self).__init__(*args, **kwargs) super(DownloadOpenRCForm, self).__init__(request, *args, **kwargs)
# Populate tenant choices # Populate tenant choices
tenant_choices = [] tenant_choices = []
for tenant in api.tenant_list(request): for tenant in api.tenant_list(request):

View File

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

View File

@ -39,3 +39,6 @@ class OpenRCView(ModalFormView):
def get_initial(self): def get_initial(self):
return {'tenant': self.request.user.tenant_id} 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 %} {% load i18n %}
{% block title %}{% trans "User Settings" %}{% endblock %} {% block title %}{% trans "User Settings" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("User Settings") %} {% include "horizon/common/_page_header.html" with title=_("User Settings") %}
{% endblock page_header %} {% endblock page_header %}
{% block settings_main %} {% block main %}
{% include "settings/user/_settings.html" %} {% include "settings/user/_settings.html" %}
{% endblock %} {% endblock %}

View File

@ -26,3 +26,6 @@ class UserSettingsView(forms.ModalFormView):
def get_initial(self): def get_initial(self):
return {'language': self.request.LANGUAGE_CODE, return {'language': self.request.LANGUAGE_CODE,
'timezone': self.request.session.get('django_timezone', 'UTC')} '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 import logging
from django import shortcuts
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from horizon import api from horizon import api
from horizon import exceptions
from horizon import forms from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -41,14 +41,16 @@ class CreateFlavor(forms.SelfHandlingForm):
eph_gb = forms.IntegerField(label=_("Ephemeral Disk GB")) eph_gb = forms.IntegerField(label=_("Ephemeral Disk GB"))
def handle(self, request, data): def handle(self, request, data):
api.flavor_create(request, try:
data['name'], flavor = api.flavor_create(request,
data['memory_mb'], data['name'],
data['vcpus'], data['memory_mb'],
data['disk_gb'], data['vcpus'],
data['flavor_id'], data['disk_gb'],
ephemeral=data['eph_gb']) data['flavor_id'],
msg = _('%s was successfully added to flavors.') % data['name'] ephemeral=data['eph_gb'])
LOG.info(msg) msg = _('%s was successfully added to flavors.') % data['name']
messages.success(request, msg) messages.success(request, msg)
return shortcuts.redirect('horizon:syspanel:flavors:index') 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 %} {% load i18n %}
{% block title %}Create Flavors{% endblock %} {% block title %}Create Flavors{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Create Flavor") %} {% include "horizon/common/_page_header.html" with title=_("Create Flavor") %}
{% endblock page_header %} {% endblock page_header %}
{% block syspanel_main %} {% block main %}
{% include "syspanel/flavors/_create.html" %} {% include "syspanel/flavors/_create.html" %}
{% endblock %} {% endblock %}

View File

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

View File

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

View File

@ -18,13 +18,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging
from horizon.dashboards.nova.images_and_snapshots.images import forms from horizon.dashboards.nova.images_and_snapshots.images import forms
LOG = logging.getLogger(__name__)
class AdminUpdateImageForm(forms.UpdateImageForm): 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 %} {% load i18n %}
{% block title %}{% trans "Images" %}{% endblock %} {% block title %}{% trans "Images" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Images") %} {% include "horizon/common/_page_header.html" with title=_("Images") %}
{% endblock page_header %} {% endblock page_header %}
{% block syspanel_main %} {% block main %}
{{ table.render }} {{ table.render }}
{% endblock %} {% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

@ -20,13 +20,12 @@
import logging import logging
from django import shortcuts
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from horizon import api from horizon import api
from horizon import exceptions from horizon import exceptions
from horizon import forms from horizon import forms
from horizon import messages
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -50,10 +49,9 @@ class AddUser(forms.SelfHandlingForm):
data['user_id'], data['user_id'],
data['role_id']) data['role_id'])
messages.success(request, _('Successfully added user to project.')) messages.success(request, _('Successfully added user to project.'))
return True
except: except:
exceptions.handle(request, _('Unable to add user to project.')) 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): class CreateTenant(forms.SelfHandlingForm):
@ -68,16 +66,16 @@ class CreateTenant(forms.SelfHandlingForm):
def handle(self, request, data): def handle(self, request, data):
try: try:
LOG.info('Creating project with name "%s"' % data['name']) LOG.info('Creating project with name "%s"' % data['name'])
api.tenant_create(request, project = api.tenant_create(request,
data['name'], data['name'],
data['description'], data['description'],
data['enabled']) data['enabled'])
messages.success(request, messages.success(request,
_('%s was successfully created.') _('%s was successfully created.')
% data['name']) % data['name'])
return project
except: except:
exceptions.handle(request, _('Unable to create project.')) exceptions.handle(request, _('Unable to create project.'))
return shortcuts.redirect('horizon:syspanel:projects:index')
class UpdateTenant(forms.SelfHandlingForm): class UpdateTenant(forms.SelfHandlingForm):
@ -92,17 +90,17 @@ class UpdateTenant(forms.SelfHandlingForm):
def handle(self, request, data): def handle(self, request, data):
try: try:
LOG.info('Updating project with id "%s"' % data['id']) LOG.info('Updating project with id "%s"' % data['id'])
api.tenant_update(request, project = api.tenant_update(request,
data['id'], data['id'],
data['name'], data['name'],
data['description'], data['description'],
data['enabled']) data['enabled'])
messages.success(request, messages.success(request,
_('%s was successfully updated.') _('%s was successfully updated.')
% data['name']) % data['name'])
return project
except: except:
exceptions.handle(request, _('Unable to update project.')) exceptions.handle(request, _('Unable to update project.'))
return shortcuts.redirect('horizon:syspanel:projects:index')
class UpdateQuotas(forms.SelfHandlingForm): class UpdateQuotas(forms.SelfHandlingForm):
@ -136,6 +134,6 @@ class UpdateQuotas(forms.SelfHandlingForm):
messages.success(request, messages.success(request,
_('Quotas for %s were successfully updated.') _('Quotas for %s were successfully updated.')
% data['tenant_id']) % data['tenant_id'])
return True
except: except:
exceptions.handle(request, _('Unable to update quotas.')) 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 %} {% load i18n %}
{% block title %}{% trans "Add User To Project" %}{% endblock %} {% block title %}{% trans "Add User To Project" %}{% endblock %}
@ -6,6 +6,6 @@
{% include "horizon/common/_page_header.html" with title=_("Add User To Project") %} {% include "horizon/common/_page_header.html" with title=_("Add User To Project") %}
{% endblock page_header %} {% endblock page_header %}
{% block syspanel_main %} {% block main %}
{% include 'syspanel/projects/_add_user.html' %} {% include 'syspanel/projects/_add_user.html' %}
{% endblock %} {% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -51,8 +51,6 @@ class TenantsViewTests(test.BaseAdminViewTests):
self.mox.StubOutWithMock(api.keystone, 'tenant_get') self.mox.StubOutWithMock(api.keystone, 'tenant_get')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_get') self.mox.StubOutWithMock(api.nova, 'tenant_quota_get')
self.mox.StubOutWithMock(api.nova, 'tenant_quota_update') 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_get(IgnoreArg(), tenant.id).AndReturn(quota)
api.nova.tenant_quota_update(IgnoreArg(), tenant.id, **quota_data) api.nova.tenant_quota_update(IgnoreArg(), tenant.id, **quota_data)
self.mox.ReplayAll() self.mox.ReplayAll()

View File

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

View File

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

View File

@ -1,4 +1,4 @@
{% extends 'syspanel/base.html' %} {% extends 'base.html' %}
{% load i18n %} {% load i18n %}
{% block title %}Services{% endblock %} {% block title %}Services{% endblock %}
@ -8,7 +8,7 @@
{% include "horizon/common/_page_header.html" with title=_("Services") refresh_link=refresh_link searchable="true" %} {% include "horizon/common/_page_header.html" with title=_("Services") refresh_link=refresh_link searchable="true" %}
{% endblock page_header %} {% endblock page_header %}
{% block syspanel_main %} {% block main %}
{{ table.render }} {{ table.render }}
{% endblock %} {% 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 import logging
from django import shortcuts
from django.contrib import messages
from django.forms import ValidationError from django.forms import ValidationError
from django.utils.translation import force_unicode, ugettext_lazy as _ from django.utils.translation import force_unicode, ugettext_lazy as _
from django.views.decorators.debug import sensitive_variables 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 api
from horizon import exceptions from horizon import exceptions
from horizon import forms from horizon import forms
from horizon import messages
from horizon.utils import validators from horizon.utils import validators
@ -37,7 +36,7 @@ LOG = logging.getLogger(__name__)
class BaseUserForm(forms.SelfHandlingForm): class BaseUserForm(forms.SelfHandlingForm):
def __init__(self, request, *args, **kwargs): def __init__(self, request, *args, **kwargs):
super(BaseUserForm, self).__init__(*args, **kwargs) super(BaseUserForm, self).__init__(request, *args, **kwargs)
# Populate tenant choices # Populate tenant choices
tenant_choices = [('', _("Select a project"))] tenant_choices = [('', _("Select a project"))]
@ -46,10 +45,6 @@ class BaseUserForm(forms.SelfHandlingForm):
tenant_choices.append((tenant.id, tenant.name)) tenant_choices.append((tenant.id, tenant.name))
self.fields['tenant_id'].choices = tenant_choices self.fields['tenant_id'].choices = tenant_choices
@classmethod
def _instantiate(cls, request, *args, **kwargs):
return cls(request, *args, **kwargs)
def clean(self): def clean(self):
'''Check to make sure password fields match.''' '''Check to make sure password fields match.'''
data = super(forms.Form, self).clean() data = super(forms.Form, self).clean()
@ -103,10 +98,9 @@ class CreateUserForm(BaseUserForm):
except: except:
exceptions.handle(request, exceptions.handle(request,
_('Unable to add user to primary project.')) _('Unable to add user to primary project.'))
return shortcuts.redirect('horizon:syspanel:users:index') return new_user
except: except:
exceptions.handle(request, _('Unable to create user.')) exceptions.handle(request, _('Unable to create user.'))
return shortcuts.redirect('horizon:syspanel:users:index')
class UpdateUserForm(BaseUserForm): class UpdateUserForm(BaseUserForm):
@ -140,7 +134,6 @@ class UpdateUserForm(BaseUserForm):
user_is_editable = api.keystone_can_edit_user() user_is_editable = api.keystone_can_edit_user()
user = data.pop('id') user = data.pop('id')
tenant = data.pop('tenant_id') tenant = data.pop('tenant_id')
data.pop('method')
if user_is_editable: if user_is_editable:
password = data.pop('password') password = data.pop('password')
@ -184,4 +177,4 @@ class UpdateUserForm(BaseUserForm):
messages.error(request, messages.error(request,
_('Unable to update %(attributes)s for the user.') _('Unable to update %(attributes)s for the user.')
% {"attributes": ", ".join(failed)}) % {"attributes": ", ".join(failed)})
return shortcuts.redirect('horizon:syspanel:users:index') return True

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,41 +18,22 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging
from django import forms from django import forms
from django.forms.forms import NON_FIELD_ERRORS from django.forms.forms import NON_FIELD_ERRORS
from django.core.urlresolvers import reverse
from django.utils import dates, timezone from django.utils import dates, timezone
from horizon import exceptions
LOG = logging.getLogger(__name__)
class SelfHandlingForm(forms.Form): class SelfHandlingForm(forms.Form):
""" """
A base :class:`Form <django:django.forms.Form>` class which includes A base :class:`Form <django:django.forms.Form>` class which includes
processing logic in its subclasses and handling errors raised during processing logic in its subclasses.
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.
""" """
method = forms.CharField(required=True, widget=forms.HiddenInput)
def __init__(self, *args, **kwargs): def __init__(self, request, *args, **kwargs):
initial = kwargs.pop('initial', {}) self.request = request
initial['method'] = self.__class__.__name__ if not hasattr(self, "handle"):
kwargs['initial'] = initial raise NotImplementedError("%s does not define a handle method."
% self.__class__.__name__)
super(SelfHandlingForm, self).__init__(*args, **kwargs) super(SelfHandlingForm, self).__init__(*args, **kwargs)
def api_error(self, message): def api_error(self, message):
@ -64,52 +45,6 @@ class SelfHandlingForm(forms.Form):
""" """
self._errors[NON_FIELD_ERRORS] = self.error_class([message]) 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): class DateForm(forms.Form):
""" A simple form for selecting a start date. """ """ A simple form for selecting a start date. """

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