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:
parent
76246c6b18
commit
df5a13c5ec
@ -364,7 +364,12 @@ def server_reboot(request, instance_id, hardness=REBOOT_HARD):
|
||||
|
||||
|
||||
def server_update(request, instance_id, name):
|
||||
return novaclient(request).servers.update(instance_id, name=name)
|
||||
response = novaclient(request).servers.update(instance_id, name=name)
|
||||
# TODO(gabriel): servers.update method doesn't return anything. :-(
|
||||
if response is None:
|
||||
return True
|
||||
else:
|
||||
return response
|
||||
|
||||
|
||||
def server_add_floating_ip(request, server, floating_ip):
|
||||
|
@ -19,22 +19,15 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django.contrib import messages
|
||||
from django import shortcuts
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
from horizon import messages
|
||||
|
||||
|
||||
class FloatingIpAllocate(forms.SelfHandlingForm):
|
||||
tenant_name = forms.CharField(widget=forms.HiddenInput())
|
||||
pool = forms.ChoiceField(label=_("Pool"))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -44,16 +37,10 @@ class FloatingIpAllocate(forms.SelfHandlingForm):
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
fip = api.tenant_floating_ip_allocate(request,
|
||||
pool=data.get('pool', None))
|
||||
LOG.info('Allocating Floating IP "%s" to project "%s"'
|
||||
% (fip.ip, data['tenant_name']))
|
||||
|
||||
fip = api.tenant_floating_ip_allocate(request, pool=data['pool'])
|
||||
messages.success(request,
|
||||
_('Successfully allocated Floating IP "%(ip)s" '
|
||||
'to project "%(project)s"')
|
||||
% {"ip": fip.ip, "project": data['tenant_name']})
|
||||
_('Allocated Floating IP %(ip)s.')
|
||||
% {"ip": fip.ip})
|
||||
return fip
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to allocate Floating IP.'))
|
||||
return shortcuts.redirect(
|
||||
'horizon:nova:access_and_security:index')
|
||||
|
@ -18,13 +18,13 @@
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.core import urlresolvers
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import messages
|
||||
from horizon import tables
|
||||
|
||||
|
||||
|
@ -23,6 +23,7 @@
|
||||
Views for managing Nova floating IPs.
|
||||
"""
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
@ -41,7 +42,10 @@ class AssociateView(workflows.WorkflowView):
|
||||
class AllocateView(forms.ModalFormView):
|
||||
form_class = FloatingIpAllocate
|
||||
template_name = 'nova/access_and_security/floating_ips/allocate.html'
|
||||
context_object_name = 'floating_ip'
|
||||
success_url = reverse_lazy('horizon:nova:access_and_security:index')
|
||||
|
||||
def get_object_display(self, obj):
|
||||
return obj.ip
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AllocateView, self).get_context_data(**kwargs)
|
||||
@ -52,11 +56,13 @@ class AllocateView(forms.ModalFormView):
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
try:
|
||||
pools = api.floating_ip_pools_list(self.request)
|
||||
if pools:
|
||||
pool_list = [(pool.name, pool.name)
|
||||
for pool in api.floating_ip_pools_list(self.request)]
|
||||
else:
|
||||
except:
|
||||
pools = []
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to retrieve floating IP pools."))
|
||||
pool_list = [(pool.name, pool.name) for pool in pools]
|
||||
if not pool_list:
|
||||
pool_list = [(None, _("No floating IP pools available."))]
|
||||
return {'tenant_name': self.request.user.tenant_name,
|
||||
'pool_list': pool_list}
|
||||
return {'pool_list': pool_list}
|
||||
|
@ -15,19 +15,23 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import workflows
|
||||
from horizon import forms
|
||||
|
||||
|
||||
ALLOCATE_URL = "horizon:nova:access_and_security:floating_ips:allocate"
|
||||
|
||||
|
||||
class AssociateIPAction(workflows.Action):
|
||||
ip_id = forms.TypedChoiceField(label=_("IP Address"),
|
||||
ip_id = forms.DynamicTypedChoiceField(label=_("IP Address"),
|
||||
coerce=int,
|
||||
empty_value=None)
|
||||
empty_value=None,
|
||||
add_item_link=ALLOCATE_URL)
|
||||
instance_id = forms.ChoiceField(label=_("Instance"))
|
||||
|
||||
class Meta:
|
||||
|
@ -18,20 +18,18 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.core import validators
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
NEW_LINES = re.compile(r"\r|\n")
|
||||
|
||||
|
||||
@ -44,14 +42,7 @@ class CreateKeypair(forms.SelfHandlingForm):
|
||||
'and hyphens.')})
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
return shortcuts.redirect(
|
||||
'horizon:nova:access_and_security:keypairs:download',
|
||||
keypair_name=data['name'])
|
||||
except:
|
||||
exceptions.handle(request,
|
||||
_('Unable to create keypair.'))
|
||||
return shortcuts.redirect(request.build_absolute_uri())
|
||||
return True # We just redirect to the download view.
|
||||
|
||||
|
||||
class ImportKeypair(forms.SelfHandlingForm):
|
||||
@ -61,14 +52,14 @@ class ImportKeypair(forms.SelfHandlingForm):
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.info('Importing keypair "%s"' % data['name'])
|
||||
# Remove any new lines in the public key
|
||||
data['public_key'] = NEW_LINES.sub("", data['public_key'])
|
||||
api.keypair_import(request, data['name'], data['public_key'])
|
||||
keypair = api.keypair_import(request,
|
||||
data['name'],
|
||||
data['public_key'])
|
||||
messages.success(request, _('Successfully imported public key: %s')
|
||||
% data['name'])
|
||||
return shortcuts.redirect(
|
||||
'horizon:nova:access_and_security:index')
|
||||
return keypair
|
||||
except:
|
||||
exceptions.handle(request,
|
||||
_('Unable to import keypair.'))
|
||||
|
@ -124,8 +124,8 @@ class KeyPairViewTests(test.TestCase):
|
||||
'name': key1_name,
|
||||
'public_key': public_key}
|
||||
url = reverse('horizon:nova:access_and_security:keypairs:import')
|
||||
self.client.post(url, formData)
|
||||
self.assertMessageCount(success=1)
|
||||
res = self.client.post(url, formData)
|
||||
self.assertMessageCount(res, success=1)
|
||||
|
||||
def test_generate_keypair_exception(self):
|
||||
keypair = self.keypairs.first()
|
||||
|
@ -24,7 +24,7 @@ Views for managing Nova keypairs.
|
||||
import logging
|
||||
|
||||
from django import http
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.views.generic import View, TemplateView
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@ -41,11 +41,20 @@ LOG = logging.getLogger(__name__)
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateKeypair
|
||||
template_name = 'nova/access_and_security/keypairs/create.html'
|
||||
success_url = 'horizon:nova:access_and_security:keypairs:download'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
kwargs={"keypair_name": self.request.POST['name']})
|
||||
|
||||
|
||||
class ImportView(forms.ModalFormView):
|
||||
form_class = ImportKeypair
|
||||
template_name = 'nova/access_and_security/keypairs/import.html'
|
||||
success_url = reverse_lazy('horizon:nova:access_and_security:index')
|
||||
|
||||
def get_object_id(self, keypair):
|
||||
return keypair.name
|
||||
|
||||
|
||||
class DownloadView(TemplateView):
|
||||
|
@ -18,24 +18,19 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.core import validators
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.forms import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from horizon.utils.validators import validate_port_range
|
||||
from horizon.utils import fields
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateGroup(forms.SelfHandlingForm):
|
||||
name = forms.CharField(label=_("Name"),
|
||||
validators=[validators.validate_slug])
|
||||
@ -43,15 +38,18 @@ class CreateGroup(forms.SelfHandlingForm):
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
api.security_group_create(request,
|
||||
sg = api.security_group_create(request,
|
||||
data['name'],
|
||||
data['description'])
|
||||
messages.success(request,
|
||||
_('Successfully created security group: %s')
|
||||
% data['name'])
|
||||
return sg
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to create security group.'))
|
||||
return shortcuts.redirect('horizon:nova:access_and_security:index')
|
||||
redirect = reverse("horizon:nova:access_and_security:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to create security group.'),
|
||||
redirect=redirect)
|
||||
|
||||
|
||||
class AddRule(forms.SelfHandlingForm):
|
||||
@ -59,6 +57,8 @@ class AddRule(forms.SelfHandlingForm):
|
||||
choices=[('tcp', 'TCP'),
|
||||
('udp', 'UDP'),
|
||||
('icmp', 'ICMP')],
|
||||
help_text=_("The protocol which this "
|
||||
"rule should be applied to."),
|
||||
widget=forms.Select(attrs={'class':
|
||||
'switchable'}))
|
||||
from_port = forms.IntegerField(label=_("From Port"),
|
||||
@ -80,7 +80,13 @@ class AddRule(forms.SelfHandlingForm):
|
||||
'data-icmp': _('Code')}),
|
||||
validators=[validate_port_range])
|
||||
|
||||
source_group = forms.ChoiceField(label=_('Source Group'), required=False)
|
||||
source_group = forms.ChoiceField(label=_('Source Group'),
|
||||
required=False,
|
||||
help_text=_("To specify an allowed IP "
|
||||
"range, select CIDR. To "
|
||||
"allow access from all "
|
||||
"members of another security "
|
||||
"group select Source Group."))
|
||||
cidr = fields.IPField(label=_("CIDR"),
|
||||
required=False,
|
||||
initial="0.0.0.0/0",
|
||||
@ -92,14 +98,13 @@ class AddRule(forms.SelfHandlingForm):
|
||||
security_group_id = forms.IntegerField(widget=forms.HiddenInput())
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
sg_list = kwargs.pop('sg_list', [])
|
||||
super(AddRule, self).__init__(*args, **kwargs)
|
||||
initials = kwargs.get("initial", {})
|
||||
current_group_id = initials.get('security_group_id', 0)
|
||||
security_groups = initials.get('security_group_list', [])
|
||||
security_groups_choices = [("", "CIDR")] # default choice is CIDR
|
||||
group_choices = [s for s in security_groups]
|
||||
if len(group_choices): # add group choice if available
|
||||
security_groups_choices.append(('Security Group', group_choices))
|
||||
# Determine if there are security groups available for the
|
||||
# source group option; add the choices and enable the option if so.
|
||||
security_groups_choices = [("", "CIDR")]
|
||||
if sg_list:
|
||||
security_groups_choices.append(('Security Group', sg_list))
|
||||
self.fields['source_group'].choices = security_groups_choices
|
||||
|
||||
def clean(self):
|
||||
@ -160,7 +165,9 @@ class AddRule(forms.SelfHandlingForm):
|
||||
data['source_group'])
|
||||
messages.success(request,
|
||||
_('Successfully added rule: %s') % unicode(rule))
|
||||
return rule
|
||||
except:
|
||||
redirect = reverse("horizon:nova:access_and_security:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to add rule to security group.'))
|
||||
return shortcuts.redirect("horizon:nova:access_and_security:index")
|
||||
_('Unable to add rule to security group.'),
|
||||
redirect=redirect)
|
||||
|
@ -100,30 +100,16 @@ class SecurityGroupsViewTests(test.TestCase):
|
||||
|
||||
def test_edit_rules_get_exception(self):
|
||||
sec_group = self.security_groups.first()
|
||||
sec_group_list = self.security_groups.list()
|
||||
|
||||
self.mox.StubOutWithMock(api, 'security_group_get')
|
||||
self.mox.StubOutWithMock(api, 'security_group_list')
|
||||
self.mox.StubOutWithMock(api, 'tenant_floating_ip_list')
|
||||
self.mox.StubOutWithMock(api.nova, 'keypair_list')
|
||||
self.mox.StubOutWithMock(api.nova, 'server_list')
|
||||
|
||||
api.nova.server_list(IsA(http.HttpRequest),
|
||||
all_tenants=True).AndReturn(self.servers.list())
|
||||
api.nova.keypair_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.keypairs.list())
|
||||
api.tenant_floating_ip_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.floating_ips.list())
|
||||
api.security_group_get(IsA(http.HttpRequest),
|
||||
sec_group.id).AndRaise(self.exceptions.nova)
|
||||
api.security_group_list(
|
||||
IsA(http.HttpRequest)).AndReturn(sec_group_list)
|
||||
api.security_group_list(
|
||||
IsA(http.HttpRequest)).AndReturn(sec_group_list)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(self.edit_url)
|
||||
self.assertRedirects(res, INDEX_URL)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
def test_edit_rules_add_rule_cidr(self):
|
||||
sec_group = self.security_groups.first()
|
||||
|
@ -24,6 +24,7 @@ Views for managing Nova instances.
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
@ -37,9 +38,11 @@ from .tables import RulesTable
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EditRulesView(tables.DataTableView):
|
||||
class EditRulesView(tables.DataTableView, forms.ModalFormView):
|
||||
table_class = RulesTable
|
||||
form_class = AddRule
|
||||
template_name = 'nova/access_and_security/security_groups/edit_rules.html'
|
||||
success_url = reverse_lazy("horizon:nova:access_and_security:index")
|
||||
|
||||
def get_data(self):
|
||||
security_group_id = int(self.kwargs['security_group_id'])
|
||||
@ -55,7 +58,12 @@ class EditRulesView(tables.DataTableView):
|
||||
_('Unable to retrieve security group.'))
|
||||
return rules
|
||||
|
||||
def handle_form(self):
|
||||
def get_initial(self):
|
||||
return {'security_group_id': self.kwargs['security_group_id']}
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(EditRulesView, self).get_form_kwargs()
|
||||
|
||||
try:
|
||||
groups = api.security_group_list(self.request)
|
||||
except:
|
||||
@ -63,36 +71,49 @@ class EditRulesView(tables.DataTableView):
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to retrieve security groups."))
|
||||
|
||||
security_groups = [(group.id, group.name) for group in groups]
|
||||
security_groups = []
|
||||
for group in groups:
|
||||
if group.id == int(self.kwargs['security_group_id']):
|
||||
security_groups.append((group.id,
|
||||
_("%s (current)") % group.name))
|
||||
else:
|
||||
security_groups.append((group.id, group.name))
|
||||
kwargs['sg_list'] = security_groups
|
||||
return kwargs
|
||||
|
||||
initial = {'security_group_id': self.kwargs['security_group_id'],
|
||||
'security_group_list': security_groups}
|
||||
return AddRule.maybe_handle(self.request, initial=initial)
|
||||
def get_form(self):
|
||||
if not hasattr(self, "_form"):
|
||||
form_class = self.get_form_class()
|
||||
self._form = super(EditRulesView, self).get_form(form_class)
|
||||
return self._form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EditRulesView, self).get_context_data(**kwargs)
|
||||
context['form'] = self.get_form()
|
||||
if self.request.is_ajax():
|
||||
context['hide'] = True
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# Form handling
|
||||
form, handled = self.handle_form()
|
||||
if handled:
|
||||
return handled
|
||||
# Table action handling
|
||||
handled = self.construct_tables()
|
||||
if handled:
|
||||
return handled
|
||||
if not self.object:
|
||||
return shortcuts.redirect("horizon:nova:access_and_security:index")
|
||||
if not self.object: # Set during table construction.
|
||||
return shortcuts.redirect(self.success_url)
|
||||
context = self.get_context_data(**kwargs)
|
||||
context['form'] = form
|
||||
context['security_group'] = self.object
|
||||
if request.is_ajax():
|
||||
context['hide'] = True
|
||||
self.template_name = ('nova/access_and_security/security_groups'
|
||||
'/_edit_rules.html')
|
||||
return self.render_to_response(context)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateGroup
|
||||
template_name = 'nova/access_and_security/security_groups/create.html'
|
||||
|
||||
def get_initial(self):
|
||||
return {"tenant_id": self.request.user.tenant_id}
|
||||
success_url = reverse_lazy('horizon:nova:access_and_security:index')
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Allocate Floating IP" %}{% endblock %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/access_and_security/floating_ips/_allocate.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Associate Floating IP" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Associate Floating IP") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_workflow.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Access & Security{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Access & Security") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
<div id="floating_ips">
|
||||
{{ floating_ips_table.render }}
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Create Keypair{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Keypair") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/access_and_security/keypairs/_create.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% blocktrans %}Download Keypair{% endblocktrans %}{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Download Keypair") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
<div class="modal-header">
|
||||
<h3>{% blocktrans %}The keypair "{{ keypair_name }}" should download automatically. If not use the link below.{% endblocktrans %}</h3>
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Import Keypair{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Import Keypair") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/access_and_security/keypairs/_import.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Create Security Group{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Security Group") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/access_and_security/security_groups/_create.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Edit Security Group Rules{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Edit Security Group Rules") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include "nova/access_and_security/security_groups/_edit_rules.html" %}
|
||||
{% endblock %}
|
||||
|
@ -24,7 +24,6 @@ Views for Instances and Volumes.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
|
@ -20,8 +20,6 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.core import validators
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@ -29,6 +27,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -65,17 +64,10 @@ class CreateContainer(forms.SelfHandlingForm):
|
||||
container,
|
||||
subfolder_name)
|
||||
messages.success(request, _("Folder created successfully."))
|
||||
url = "horizon:nova:containers:object_index"
|
||||
if remainder:
|
||||
remainder = remainder.rstrip("/")
|
||||
remainder += "/"
|
||||
return shortcuts.redirect(url, container, remainder)
|
||||
|
||||
return True
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to create container.'))
|
||||
|
||||
return shortcuts.redirect("horizon:nova:containers:index")
|
||||
|
||||
|
||||
class UploadObject(forms.SelfHandlingForm):
|
||||
path = forms.CharField(max_length=255,
|
||||
@ -101,10 +93,9 @@ class UploadObject(forms.SelfHandlingForm):
|
||||
obj.metadata['orig-filename'] = object_file.name
|
||||
obj.sync_metadata()
|
||||
messages.success(request, _("Object was successfully uploaded."))
|
||||
return obj
|
||||
except:
|
||||
exceptions.handle(request, _("Unable to upload object."))
|
||||
return shortcuts.redirect("horizon:nova:containers:object_index",
|
||||
data['container_name'], data['path'])
|
||||
|
||||
|
||||
class CopyObject(forms.SelfHandlingForm):
|
||||
@ -160,12 +151,13 @@ class CopyObject(forms.SelfHandlingForm):
|
||||
messages.success(request,
|
||||
_('Copied "%(orig)s" to "%(dest)s" as "%(new)s".')
|
||||
% vals)
|
||||
return True
|
||||
except exceptions.HorizonException, exc:
|
||||
messages.error(request, exc)
|
||||
return shortcuts.redirect(object_index, orig_container)
|
||||
raise exceptions.Http302(reverse(object_index,
|
||||
args=[orig_container]))
|
||||
except:
|
||||
redirect = reverse(object_index, args=(orig_container,))
|
||||
exceptions.handle(request,
|
||||
_("Unable to copy object."),
|
||||
redirect=redirect)
|
||||
return shortcuts.redirect(object_index, new_container, data['path'])
|
||||
|
@ -17,13 +17,13 @@
|
||||
import logging
|
||||
|
||||
from cloudfiles.errors import ContainerNotEmpty
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.template.defaultfilters import filesizeformat
|
||||
from django.utils import http
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import messages
|
||||
from horizon import tables
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Copy Object" %}{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Copy Object") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/containers/_copy.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Create Container{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Container") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include "nova/containers/_create.html" %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Objects" %}{% endblock %}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
</div>
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
<div id="subfolders">
|
||||
{{ subfolders_table.render }}
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Containers{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Containers") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Upload Object" %}{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Upload Objects") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/containers/_upload.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -93,7 +93,9 @@ class ContainerViewTests(test.TestCase):
|
||||
'method': forms.CreateContainer.__name__}
|
||||
res = self.client.post(reverse('horizon:nova:containers:create'),
|
||||
formData)
|
||||
self.assertRedirectsNoFollow(res, CONTAINER_INDEX_URL)
|
||||
url = reverse('horizon:nova:containers:object_index',
|
||||
args=[self.containers.first().name])
|
||||
self.assertRedirectsNoFollow(res, url)
|
||||
|
||||
|
||||
class ObjectViewTests(test.TestCase):
|
||||
|
@ -63,6 +63,17 @@ class IndexView(tables.DataTableView):
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateContainer
|
||||
template_name = 'nova/containers/create.html'
|
||||
success_url = "horizon:nova:containers:object_index"
|
||||
|
||||
def get_success_url(self):
|
||||
parent = self.request.POST.get('parent', None)
|
||||
if parent:
|
||||
container, slash, remainder = parent.partition("/")
|
||||
if remainder and not remainder.endswith("/"):
|
||||
remainder = "".join([remainder, "/"])
|
||||
return reverse(self.success_url, args=(container, remainder))
|
||||
else:
|
||||
return reverse(self.success_url, args=[self.request.POST['name']])
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(CreateView, self).get_initial()
|
||||
@ -132,6 +143,12 @@ class ObjectIndexView(tables.MultiTableView):
|
||||
class UploadView(forms.ModalFormView):
|
||||
form_class = UploadObject
|
||||
template_name = 'nova/containers/upload.html'
|
||||
success_url = "horizon:nova:containers:object_index"
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.request.POST['container_name'],
|
||||
self.request.POST.get('path', '')))
|
||||
|
||||
def get_initial(self):
|
||||
return {"container_name": self.kwargs["container_name"],
|
||||
@ -172,6 +189,12 @@ def object_download(request, container_name, object_path):
|
||||
class CopyView(forms.ModalFormView):
|
||||
form_class = CopyObject
|
||||
template_name = 'nova/containers/copy.html'
|
||||
success_url = "horizon:nova:containers:object_index"
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.request.POST['new_container_name'],
|
||||
self.request.POST.get('path', '')))
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(CopyView, self).get_form_kwargs()
|
||||
|
@ -24,26 +24,23 @@ Views for managing Nova images.
|
||||
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateImageForm(forms.SelfHandlingForm):
|
||||
completion_view = 'horizon:nova:images_and_snapshots:index'
|
||||
|
||||
name = forms.CharField(max_length="255", label=_("Name"), required=True)
|
||||
copy_from = forms.CharField(max_length="255",
|
||||
label=_("Image Location"),
|
||||
help_text=_("An external (HTTP) URL where"
|
||||
" the image should be loaded from."),
|
||||
help_text=_("An external (HTTP) URL to load "
|
||||
"the image from."),
|
||||
required=True)
|
||||
disk_format = forms.ChoiceField(label=_('Format'),
|
||||
required=True,
|
||||
@ -103,19 +100,16 @@ class CreateImageForm(forms.SelfHandlingForm):
|
||||
'name': data['name']}
|
||||
|
||||
try:
|
||||
api.glance.image_create(request, **meta)
|
||||
image = api.glance.image_create(request, **meta)
|
||||
messages.success(request,
|
||||
_('Your image %s has been queued for creation.' %
|
||||
data['name']))
|
||||
return image
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to create new image.'))
|
||||
|
||||
return shortcuts.redirect(self.get_success_url())
|
||||
|
||||
|
||||
class UpdateImageForm(forms.SelfHandlingForm):
|
||||
completion_view = 'horizon:nova:images_and_snapshots:index'
|
||||
|
||||
image_id = forms.CharField(widget=forms.HiddenInput())
|
||||
name = forms.CharField(max_length="255", label=_("Name"))
|
||||
kernel = forms.CharField(max_length="36", label=_("Kernel ID"),
|
||||
@ -139,13 +133,17 @@ class UpdateImageForm(forms.SelfHandlingForm):
|
||||
public = forms.BooleanField(label=_("Public"), required=False)
|
||||
|
||||
def handle(self, request, data):
|
||||
# TODO add public flag to image meta properties
|
||||
image_id = data['image_id']
|
||||
error_updating = _('Unable to update image "%s".')
|
||||
|
||||
if data['disk_format'] in ['aki', 'ari', 'ami']:
|
||||
container_format = data['disk_format']
|
||||
else:
|
||||
container_format = 'bare'
|
||||
|
||||
meta = {'is_public': data['public'],
|
||||
'disk_format': data['disk_format'],
|
||||
'container_format': 'bare',
|
||||
'container_format': container_format,
|
||||
'name': data['name'],
|
||||
'properties': {}}
|
||||
if data['kernel']:
|
||||
@ -154,13 +152,13 @@ class UpdateImageForm(forms.SelfHandlingForm):
|
||||
meta['properties']['ramdisk_id'] = data['ramdisk']
|
||||
if data['architecture']:
|
||||
meta['properties']['architecture'] = data['architecture']
|
||||
|
||||
try:
|
||||
# Ensure we do not delete properties that have already been
|
||||
# set on an image.
|
||||
meta['features'] = {'X-Glance-Registry-Purge-Props': False}
|
||||
api.image_update(request, image_id, **meta)
|
||||
meta['purge_props'] = False
|
||||
|
||||
try:
|
||||
image = api.image_update(request, image_id, **meta)
|
||||
messages.success(request, _('Image was successfully updated.'))
|
||||
return image
|
||||
except:
|
||||
exceptions.handle(request, error_updating % image_id)
|
||||
return shortcuts.redirect(self.get_success_url())
|
||||
|
@ -24,7 +24,7 @@ Views for managing Nova images.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
@ -43,35 +43,39 @@ class CreateView(forms.ModalFormView):
|
||||
form_class = CreateImageForm
|
||||
template_name = 'nova/images_and_snapshots/images/create.html'
|
||||
context_object_name = 'image'
|
||||
success_url = reverse_lazy("horizon:nova:images_and_snapshots:index")
|
||||
|
||||
|
||||
class UpdateView(forms.ModalFormView):
|
||||
form_class = UpdateImageForm
|
||||
template_name = 'nova/images_and_snapshots/images/update.html'
|
||||
context_object_name = 'image'
|
||||
success_url = reverse_lazy("horizon:nova:images_and_snapshots:index")
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
def get_object(self):
|
||||
if not hasattr(self, "_object"):
|
||||
try:
|
||||
self.object = api.image_get(self.request, kwargs['image_id'])
|
||||
self._object = api.image_get(self.request,
|
||||
self.kwargs['image_id'])
|
||||
except:
|
||||
msg = _('Unable to retrieve image.')
|
||||
redirect = reverse('horizon:nova:images_and_snapshots:index')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self.object
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
context['image'] = self.get_object()
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
properties = self.object.properties
|
||||
# NOTE(gabriel): glanceclient currently treats "is_public" as a string
|
||||
# rather than a boolean. This should be fixed in the client.
|
||||
public = self.object.is_public == "True"
|
||||
image = self.get_object()
|
||||
return {'image_id': self.kwargs['image_id'],
|
||||
'name': self.object.name,
|
||||
'kernel': properties.get('kernel_id', ''),
|
||||
'ramdisk': properties.get('ramdisk_id', ''),
|
||||
'architecture': properties.get('architecture', ''),
|
||||
'container_format': self.object.container_format,
|
||||
'disk_format': self.object.disk_format,
|
||||
'public': public}
|
||||
'name': image.name,
|
||||
'kernel': image.properties.get('kernel_id', ''),
|
||||
'ramdisk': image.properties.get('ramdisk_id', ''),
|
||||
'architecture': image.properties.get('architecture', ''),
|
||||
'disk_format': image.disk_format,
|
||||
'public': image.is_public == "True"}
|
||||
|
||||
|
||||
class DetailView(tabs.TabView):
|
||||
|
@ -20,21 +20,19 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateSnapshot(forms.SelfHandlingForm):
|
||||
tenant_id = forms.CharField(widget=forms.HiddenInput())
|
||||
instance_id = forms.CharField(label=_("Instance ID"),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
@ -42,14 +40,15 @@ class CreateSnapshot(forms.SelfHandlingForm):
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
api.snapshot_create(request, data['instance_id'], data['name'])
|
||||
snapshot = api.snapshot_create(request,
|
||||
data['instance_id'],
|
||||
data['name'])
|
||||
# NOTE(gabriel): This API call is only to display a pretty name.
|
||||
instance = api.server_get(request, data['instance_id'])
|
||||
vals = {"name": data['name'], "inst": instance.name}
|
||||
messages.success(request, _('Snapshot "%(name)s" created for '
|
||||
'instance "%(inst)s"') % vals)
|
||||
return shortcuts.redirect('horizon:nova:images_and_snapshots:'
|
||||
'index')
|
||||
return snapshot
|
||||
except:
|
||||
redirect = reverse("horizon:nova:instances:index")
|
||||
exceptions.handle(request,
|
||||
|
@ -42,18 +42,6 @@ class SnapshotsViewTests(test.TestCase):
|
||||
self.assertTemplateUsed(res,
|
||||
'nova/images_and_snapshots/snapshots/create.html')
|
||||
|
||||
def test_create_snapshot_get_with_invalid_status(self):
|
||||
server = self.servers.get(status='BUILD')
|
||||
self.mox.StubOutWithMock(api, 'server_get')
|
||||
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:nova:images_and_snapshots:snapshots:create',
|
||||
args=[server.id])
|
||||
res = self.client.get(url)
|
||||
redirect = reverse("horizon:nova:instances:index")
|
||||
self.assertRedirectsNoFollow(res, redirect)
|
||||
|
||||
def test_create_get_server_exception(self):
|
||||
server = self.servers.first()
|
||||
self.mox.StubOutWithMock(api, 'server_get')
|
||||
@ -76,7 +64,6 @@ class SnapshotsViewTests(test.TestCase):
|
||||
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
||||
api.snapshot_create(IsA(http.HttpRequest), server.id, snapshot.name) \
|
||||
.AndReturn(snapshot)
|
||||
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'method': 'CreateSnapshot',
|
||||
@ -95,7 +82,6 @@ class SnapshotsViewTests(test.TestCase):
|
||||
|
||||
self.mox.StubOutWithMock(api, 'server_get')
|
||||
self.mox.StubOutWithMock(api, 'snapshot_create')
|
||||
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
||||
api.snapshot_create(IsA(http.HttpRequest), server.id, snapshot.name) \
|
||||
.AndRaise(self.exceptions.nova)
|
||||
self.mox.ReplayAll()
|
||||
|
@ -24,7 +24,7 @@ Views for managing Nova instance snapshots.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
@ -39,24 +39,24 @@ LOG = logging.getLogger(__name__)
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateSnapshot
|
||||
template_name = 'nova/images_and_snapshots/snapshots/create.html'
|
||||
success_url = reverse_lazy("horizon:nova:images_and_snapshots:index")
|
||||
|
||||
def get_object(self):
|
||||
if not hasattr(self, "_object"):
|
||||
try:
|
||||
self._object = api.server_get(self.request,
|
||||
self.kwargs["instance_id"])
|
||||
except:
|
||||
redirect = reverse('horizon:nova:instances:index')
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to retrieve instance."),
|
||||
redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_initial(self):
|
||||
redirect = reverse('horizon:nova:instances:index')
|
||||
instance_id = self.kwargs["instance_id"]
|
||||
try:
|
||||
self.instance = api.server_get(self.request, instance_id)
|
||||
except:
|
||||
self.instance = None
|
||||
msg = _("Unable to retrieve instance.")
|
||||
exceptions.handle(self.request, msg, redirect)
|
||||
if self.instance.status != api.nova.INSTANCE_ACTIVE_STATE:
|
||||
msg = _('To create a snapshot, the instance must be in '
|
||||
'the "%s" state.') % api.nova.INSTANCE_ACTIVE_STATE
|
||||
raise exceptions.Http302(redirect, message=msg)
|
||||
return {"instance_id": instance_id,
|
||||
"tenant_id": self.request.user.tenant_id}
|
||||
return {"instance_id": self.kwargs["instance_id"]}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateView, self).get_context_data(**kwargs)
|
||||
context['instance'] = self.instance
|
||||
context['instance'] = self.get_object()
|
||||
return context
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create An Image" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create An Image") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/images_and_snapshots/images/_create.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Image Detail "%}{% endblock %}
|
||||
@ -7,7 +7,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title="Image Detail" %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
{{ tab_group.render }}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Update Image" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Update Image") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/images_and_snapshots/images/_update.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Images & Snapshots" %}{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Images & Snapshots") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
<div class="images">
|
||||
{{ images_table.render }}
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Snapshot" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create a Snapshot") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/images_and_snapshots/snapshots/_create.html' %}
|
||||
{% endblock %}
|
||||
|
@ -20,13 +20,13 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -39,10 +39,12 @@ class UpdateInstance(forms.SelfHandlingForm):
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
api.server_update(request, data['instance'], data['name'])
|
||||
server = api.server_update(request, data['instance'], data['name'])
|
||||
messages.success(request,
|
||||
_('Instance "%s" updated.') % data['name'])
|
||||
return server
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to update instance.'))
|
||||
|
||||
return shortcuts.redirect('horizon:nova:instances:index')
|
||||
redirect = reverse("horizon:nova:instances:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to update instance.'),
|
||||
redirect=redirect)
|
||||
|
@ -2,7 +2,7 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}update_instance_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:nova:instances:update instance.id %}{% endblock %}
|
||||
{% block form_action %}{% url horizon:nova:instances:update instance_id %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Edit Instance" %}{% endblock %}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n sizeformat %}
|
||||
{% block title %}{% trans "Instance Detail" %}{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title="Instance Detail: "|add:instance.name %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
{{ tab_group.render }}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Instances" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Instances") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Launch Instance" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Launch Instance") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'horizon/common/_workflow.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Update Instance" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Update Instance") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/instances/_update.html' %}
|
||||
{% endblock %}
|
||||
|
@ -491,28 +491,22 @@ class InstanceTests(test.TestCase):
|
||||
'server_delete',)})
|
||||
def test_create_instance_snapshot(self):
|
||||
server = self.servers.first()
|
||||
snapshot_server = deepcopy(server)
|
||||
setattr(snapshot_server, 'OS-EXT-STS:task_state',
|
||||
"IMAGE_SNAPSHOT")
|
||||
|
||||
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
||||
api.snapshot_create(IsA(http.HttpRequest),
|
||||
server.id,
|
||||
"snapshot1")
|
||||
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
||||
"snapshot1").AndReturn(self.snapshots.first())
|
||||
|
||||
api.snapshot_list_detailed(IsA(http.HttpRequest),
|
||||
marker=None).AndReturn([[], False])
|
||||
api.image_list_detailed(IsA(http.HttpRequest),
|
||||
marker=None).AndReturn([[], False])
|
||||
api.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
|
||||
api.server_list(IsA(http.HttpRequest)).AndReturn([snapshot_server])
|
||||
api.flavor_list(IgnoreArg()).AndReturn(self.flavors.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'instance_id': server.id,
|
||||
'method': 'CreateSnapshot',
|
||||
'tenant_id': server.tenant_id,
|
||||
'name': 'snapshot1'}
|
||||
url = reverse('horizon:nova:images_and_snapshots:snapshots:create',
|
||||
args=[server.id])
|
||||
@ -520,10 +514,6 @@ class InstanceTests(test.TestCase):
|
||||
res = self.client.post(url, formData)
|
||||
self.assertRedirects(res, redir_url)
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertContains(res, '<td class="status_unknown sortable">'
|
||||
'Snapshotting</td>', 1)
|
||||
|
||||
@test.create_stubs({api: ('server_get',)})
|
||||
def test_instance_update_get(self):
|
||||
server = self.servers.first()
|
||||
@ -532,12 +522,10 @@ class InstanceTests(test.TestCase):
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:nova:instances:update',
|
||||
args=[server.id])
|
||||
url = reverse('horizon:nova:instances:update', args=[server.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(res,
|
||||
'nova/instances/update.html')
|
||||
self.assertTemplateUsed(res, 'nova/instances/update.html')
|
||||
|
||||
@test.create_stubs({api: ('server_get',)})
|
||||
def test_instance_update_get_server_get_exception(self):
|
||||
@ -559,7 +547,9 @@ class InstanceTests(test.TestCase):
|
||||
server = self.servers.first()
|
||||
|
||||
api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
||||
api.server_update(IsA(http.HttpRequest), server.id, server.name)
|
||||
api.server_update(IsA(http.HttpRequest),
|
||||
server.id,
|
||||
server.name).AndReturn(server)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
@ -25,7 +25,7 @@ import logging
|
||||
|
||||
from django import http
|
||||
from django import shortcuts
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
@ -126,22 +126,28 @@ class UpdateView(forms.ModalFormView):
|
||||
form_class = UpdateInstance
|
||||
template_name = 'nova/instances/update.html'
|
||||
context_object_name = 'instance'
|
||||
success_url = reverse_lazy("horizon:nova:instances:index")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
context["instance_id"] = self.kwargs['instance_id']
|
||||
return context
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "object"):
|
||||
if not hasattr(self, "_object"):
|
||||
instance_id = self.kwargs['instance_id']
|
||||
try:
|
||||
self.object = api.server_get(self.request, instance_id)
|
||||
self._object = api.server_get(self.request, instance_id)
|
||||
except:
|
||||
redirect = reverse("horizon:nova:instances:index")
|
||||
msg = _('Unable to retrieve instance details.')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self.object
|
||||
return self._object
|
||||
|
||||
def get_initial(self):
|
||||
return {'instance': self.kwargs['instance_id'],
|
||||
'tenant_id': self.request.user.tenant_id,
|
||||
'name': getattr(self.object, 'name', '')}
|
||||
'name': getattr(self.get_object(), 'name', '')}
|
||||
|
||||
|
||||
class DetailView(tabs.TabView):
|
||||
|
@ -18,14 +18,14 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django import forms
|
||||
from django.utils.text import normalize_newlines
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon.openstack.common import jsonutils
|
||||
from horizon import forms
|
||||
from horizon import workflows
|
||||
from horizon.openstack.common import jsonutils
|
||||
|
||||
|
||||
class SelectProjectUserAction(workflows.Action):
|
||||
@ -320,11 +320,15 @@ class SetInstanceDetails(workflows.Step):
|
||||
return context
|
||||
|
||||
|
||||
KEYPAIR_IMPORT_URL = "horizon:nova:access_and_security:keypairs:import"
|
||||
|
||||
|
||||
class SetAccessControlsAction(workflows.Action):
|
||||
keypair = forms.ChoiceField(label=_("Keypair"),
|
||||
keypair = forms.DynamicChoiceField(label=_("Keypair"),
|
||||
required=False,
|
||||
help_text=_("Which keypair to use for "
|
||||
"authentication."))
|
||||
"authentication."),
|
||||
add_item_link=KEYPAIR_IMPORT_URL)
|
||||
groups = forms.MultipleChoiceField(label=_("Security Groups"),
|
||||
required=True,
|
||||
initial=["default"],
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Instance Overview{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Overview") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include "horizon/common/_usage_summary.html" %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -15,12 +15,3 @@
|
||||
{% endif %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% include 'horizon/common/_sidebar.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "horizon/_messages.html" %}
|
||||
{% block dash_main %}{% endblock %}
|
||||
{% endblock %}
|
||||
|
@ -7,14 +7,14 @@
|
||||
Views for managing Nova volumes.
|
||||
"""
|
||||
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.forms import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import forms
|
||||
from horizon import exceptions
|
||||
from horizon import messages
|
||||
|
||||
from ..instances.tables import ACTIVE_STATES
|
||||
|
||||
@ -48,19 +48,19 @@ class CreateForm(forms.SelfHandlingForm):
|
||||
' volumes.')
|
||||
raise ValidationError(error_message)
|
||||
|
||||
api.volume_create(request, data['size'], data['name'],
|
||||
volume = api.volume_create(request,
|
||||
data['size'],
|
||||
data['name'],
|
||||
data['description'])
|
||||
message = 'Creating volume "%s"' % data['name']
|
||||
|
||||
messages.info(request, message)
|
||||
return volume
|
||||
except ValidationError, e:
|
||||
return self.api_error(e.messages[0])
|
||||
except:
|
||||
exceptions.handle(request, ignore=True)
|
||||
return self.api_error(_("Unable to create volume."))
|
||||
|
||||
return shortcuts.redirect("horizon:nova:volumes:index")
|
||||
|
||||
|
||||
class AttachForm(forms.SelfHandlingForm):
|
||||
instance = forms.ChoiceField(label="Attach to Instance",
|
||||
@ -113,10 +113,12 @@ class AttachForm(forms.SelfHandlingForm):
|
||||
"inst": instance_name,
|
||||
"dev": data['device']}
|
||||
messages.info(request, message)
|
||||
return True
|
||||
except:
|
||||
redirect = reverse("horizon:nova:volumes:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to attach volume.'))
|
||||
return shortcuts.redirect("horizon:nova:volumes:index")
|
||||
_('Unable to attach volume.'),
|
||||
redirect=redirect)
|
||||
|
||||
|
||||
class CreateSnapshotForm(forms.SelfHandlingForm):
|
||||
@ -134,15 +136,16 @@ class CreateSnapshotForm(forms.SelfHandlingForm):
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
api.volume_snapshot_create(request,
|
||||
snapshot = api.volume_snapshot_create(request,
|
||||
data['volume_id'],
|
||||
data['name'],
|
||||
data['description'])
|
||||
|
||||
message = _('Creating volume snapshot "%s"') % data['name']
|
||||
messages.info(request, message)
|
||||
return snapshot
|
||||
except:
|
||||
redirect = reverse("horizon:nova:images_and_snapshots:index")
|
||||
exceptions.handle(request,
|
||||
_('Unable to create volume snapshot.'))
|
||||
|
||||
return shortcuts.redirect("horizon:nova:images_and_snapshots:index")
|
||||
_('Unable to create volume snapshot.'),
|
||||
redirect=redirect)
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Manage Volume Attachments{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Manage Volume Attachments") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/volumes/_attach.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Create Volume{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create a Volume") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/volumes/_create.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Volume Snapshot" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create a Volume Snapshot") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{% include 'nova/volumes/_create_snapshot.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Volume Details" %}{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Volume Detail") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
{{ tab_group.render }}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Volumes" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Volumes") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -29,6 +29,7 @@ from horizon import test
|
||||
class VolumeViewTests(test.TestCase):
|
||||
@test.create_stubs({api: ('tenant_quota_usages', 'volume_create',)})
|
||||
def test_create_volume(self):
|
||||
volume = self.volumes.first()
|
||||
usage = {'gigabytes': {'available': 250}, 'volumes': {'available': 6}}
|
||||
formData = {'name': u'A Volume I Am Making',
|
||||
'description': u'This is a volume I am making for a test.',
|
||||
@ -39,7 +40,7 @@ class VolumeViewTests(test.TestCase):
|
||||
api.volume_create(IsA(http.HttpRequest),
|
||||
formData['size'],
|
||||
formData['name'],
|
||||
formData['description'])
|
||||
formData['description']).AndReturn(volume)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
@ -20,6 +20,8 @@ Views for managing Nova volumes.
|
||||
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
@ -77,6 +79,7 @@ class DetailView(tabs.TabView):
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateForm
|
||||
template_name = 'nova/volumes/create.html'
|
||||
success_url = reverse_lazy("horizon:nova:volumes:index")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateView, self).get_context_data(**kwargs)
|
||||
@ -84,24 +87,26 @@ class CreateView(forms.ModalFormView):
|
||||
context['usages'] = api.tenant_quota_usages(self.request)
|
||||
except:
|
||||
exceptions.handle(self.request)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class CreateSnapshotView(forms.ModalFormView):
|
||||
form_class = CreateSnapshotForm
|
||||
template_name = 'nova/volumes/create_snapshot.html'
|
||||
success_url = reverse_lazy("horizon:nova:images_and_snapshots:index")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return {'volume_id': kwargs['volume_id']}
|
||||
return {'volume_id': self.kwargs['volume_id']}
|
||||
|
||||
def get_initial(self):
|
||||
return {'volume_id': self.kwargs["volume_id"]}
|
||||
|
||||
|
||||
class EditAttachmentsView(tables.DataTableView):
|
||||
class EditAttachmentsView(tables.DataTableView, forms.ModalFormView):
|
||||
table_class = AttachmentsTable
|
||||
form_class = AttachForm
|
||||
template_name = 'nova/volumes/attach.html'
|
||||
success_url = reverse_lazy("horizon:nova:volumes:index")
|
||||
|
||||
def get_object(self):
|
||||
if not hasattr(self, "_object"):
|
||||
@ -124,35 +129,40 @@ class EditAttachmentsView(tables.DataTableView):
|
||||
_('Unable to retrieve volume information.'))
|
||||
return attachments
|
||||
|
||||
def get_initial(self):
|
||||
try:
|
||||
instances = api.nova.server_list(self.request)
|
||||
except:
|
||||
instances = []
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to retrieve attachment information."))
|
||||
return {'volume': self.get_object(),
|
||||
'instances': instances}
|
||||
|
||||
def get_form(self):
|
||||
if not hasattr(self, "_form"):
|
||||
form_class = self.get_form_class()
|
||||
self._form = super(EditAttachmentsView, self).get_form(form_class)
|
||||
return self._form
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EditAttachmentsView, self).get_context_data(**kwargs)
|
||||
context['form'] = self.form
|
||||
context['form'] = self.get_form()
|
||||
context['volume'] = self.get_object()
|
||||
if self.request.is_ajax():
|
||||
context['hide'] = True
|
||||
return context
|
||||
|
||||
def handle_form(self):
|
||||
instances = api.nova.server_list(self.request)
|
||||
initial = {'volume': self.get_object(),
|
||||
'instances': instances}
|
||||
return AttachForm.maybe_handle(self.request, initial=initial)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.form, handled = self.handle_form()
|
||||
if handled:
|
||||
return handled
|
||||
# Table action handling
|
||||
handled = self.construct_tables()
|
||||
if handled:
|
||||
return handled
|
||||
context = self.get_context_data(**kwargs)
|
||||
context['form'] = self.form
|
||||
if request.is_ajax():
|
||||
context['hide'] = True
|
||||
self.template_name = ('nova/volumes'
|
||||
'/_attach.html')
|
||||
return self.render_to_response(context)
|
||||
return self.render_to_response(self.get_context_data(**kwargs))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form, handled = self.handle_form()
|
||||
if handled:
|
||||
return handled
|
||||
return super(EditAttachmentsView, self).post(request, *args, **kwargs)
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
@ -34,14 +34,8 @@ LOG = logging.getLogger(__name__)
|
||||
class DownloadX509Credentials(forms.SelfHandlingForm):
|
||||
tenant = forms.ChoiceField(label=_("Select a Project"))
|
||||
|
||||
# forms.SelfHandlingForm doesn't pass request object as the first argument
|
||||
# to the class __init__ method, which causes form to explode.
|
||||
@classmethod
|
||||
def _instantiate(cls, request, *args, **kwargs):
|
||||
return cls(request, *args, **kwargs)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(DownloadX509Credentials, self).__init__(*args, **kwargs)
|
||||
super(DownloadX509Credentials, self).__init__(request, *args, **kwargs)
|
||||
# Populate tenant choices
|
||||
tenant_choices = []
|
||||
try:
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'settings/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Download EC2 Credentials" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Download EC2 Credentials") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block settings_main %}
|
||||
{% block main %}
|
||||
{% include "settings/ec2/download_form.html" %}
|
||||
{% endblock %}
|
||||
|
@ -26,3 +26,6 @@ LOG = logging.getLogger(__name__)
|
||||
class IndexView(forms.ModalFormView):
|
||||
form_class = DownloadX509Credentials
|
||||
template_name = 'settings/ec2/index.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
return form.handle(self.request, form.cleaned_data)
|
||||
|
@ -21,11 +21,11 @@
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -34,14 +34,8 @@ LOG = logging.getLogger(__name__)
|
||||
class DownloadOpenRCForm(forms.SelfHandlingForm):
|
||||
tenant = forms.ChoiceField(label=_("Select a Project"))
|
||||
|
||||
# forms.SelfHandlingForm doesn't pass request object as the first argument
|
||||
# to the class __init__ method, which causes form to explode.
|
||||
@classmethod
|
||||
def _instantiate(cls, request, *args, **kwargs):
|
||||
return cls(request, *args, **kwargs)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(DownloadOpenRCForm, self).__init__(*args, **kwargs)
|
||||
super(DownloadOpenRCForm, self).__init__(request, *args, **kwargs)
|
||||
# Populate tenant choices
|
||||
tenant_choices = []
|
||||
for tenant in api.tenant_list(request):
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'settings/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "OpenStack API" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("OpenStack API") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block settings_main %}
|
||||
{% block main %}
|
||||
{% include "settings/project/_openrc.html" %}
|
||||
{% endblock %}
|
||||
|
@ -39,3 +39,6 @@ class OpenRCView(ModalFormView):
|
||||
|
||||
def get_initial(self):
|
||||
return {'tenant': self.request.user.tenant_id}
|
||||
|
||||
def form_valid(self, form):
|
||||
return form.handle(self.request, form.cleaned_data)
|
||||
|
@ -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 %}
|
@ -1,4 +1,4 @@
|
||||
{% extends 'settings/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "User Settings" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("User Settings") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block settings_main %}
|
||||
{% block main %}
|
||||
{% include "settings/user/_settings.html" %}
|
||||
{% endblock %}
|
||||
|
@ -26,3 +26,6 @@ class UserSettingsView(forms.ModalFormView):
|
||||
def get_initial(self):
|
||||
return {'language': self.request.LANGUAGE_CODE,
|
||||
'timezone': self.request.session.get('django_timezone', 'UTC')}
|
||||
|
||||
def form_valid(self, form):
|
||||
return form.handle(self.request, form.cleaned_data)
|
||||
|
@ -20,12 +20,12 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -41,7 +41,8 @@ class CreateFlavor(forms.SelfHandlingForm):
|
||||
eph_gb = forms.IntegerField(label=_("Ephemeral Disk GB"))
|
||||
|
||||
def handle(self, request, data):
|
||||
api.flavor_create(request,
|
||||
try:
|
||||
flavor = api.flavor_create(request,
|
||||
data['name'],
|
||||
data['memory_mb'],
|
||||
data['vcpus'],
|
||||
@ -49,6 +50,7 @@ class CreateFlavor(forms.SelfHandlingForm):
|
||||
data['flavor_id'],
|
||||
ephemeral=data['eph_gb'])
|
||||
msg = _('%s was successfully added to flavors.') % data['name']
|
||||
LOG.info(msg)
|
||||
messages.success(request, msg)
|
||||
return shortcuts.redirect('horizon:syspanel:flavors:index')
|
||||
return flavor
|
||||
except:
|
||||
exceptions.handle(request, _("Unable to create flavor"))
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Create Flavors{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Flavor") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{% include "syspanel/flavors/_create.html" %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Flavors{% endblock %}
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Flavors") refresh_link=refresh_link searchable="true" %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -20,11 +20,11 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from novaclient import exceptions as api_exceptions
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tables
|
||||
from .forms import CreateFlavor
|
||||
@ -43,15 +43,9 @@ class IndexView(tables.DataTableView):
|
||||
flavors = []
|
||||
try:
|
||||
flavors = api.flavor_list(request)
|
||||
except api_exceptions.Unauthorized, e:
|
||||
LOG.exception('Unauthorized attempt to access flavor list.')
|
||||
messages.error(request, _('Unauthorized.'))
|
||||
except Exception, e:
|
||||
LOG.exception('Exception while fetching usage info')
|
||||
if not hasattr(e, 'message'):
|
||||
e.message = str(e)
|
||||
messages.error(request, _('Unable to get flavor list: %s') %
|
||||
e.message)
|
||||
except:
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve flavor list.'))
|
||||
flavors.sort(key=lambda x: x.id, reverse=True)
|
||||
return flavors
|
||||
|
||||
@ -59,11 +53,15 @@ class IndexView(tables.DataTableView):
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateFlavor
|
||||
template_name = 'syspanel/flavors/create.html'
|
||||
success_url = reverse_lazy('horizon:syspanel:flavors:index')
|
||||
|
||||
def get_initial(self):
|
||||
# TODO(tres): Get rid of this hacky bit of nonsense after flavors get
|
||||
# converted to nova client.
|
||||
# TODO(tres): Get rid of this hacky bit of nonsense after flavors
|
||||
# id handling gets fixed.
|
||||
try:
|
||||
flavors = api.flavor_list(self.request)
|
||||
except:
|
||||
exceptions.handle(self.request, ignore=True)
|
||||
if flavors:
|
||||
largest_id = max(flavors, key=lambda f: f.id).id
|
||||
return {'flavor_id': int(largest_id) + 1}
|
||||
|
@ -18,13 +18,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from horizon.dashboards.nova.images_and_snapshots.images import forms
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AdminUpdateImageForm(forms.UpdateImageForm):
|
||||
completion_view = 'horizon:syspanel:images:index'
|
||||
pass
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Images" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Images") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Update Image" %}{% endblock %}
|
||||
@ -7,6 +7,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Update Image") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{% include 'syspanel/images/_update.html' %}
|
||||
{% endblock %}
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
@ -57,6 +58,7 @@ class IndexView(tables.DataTableView):
|
||||
class UpdateView(views.UpdateView):
|
||||
template_name = 'syspanel/images/update.html'
|
||||
form_class = AdminUpdateImageForm
|
||||
success_url = reverse_lazy('horizon:syspanel:images:index')
|
||||
|
||||
|
||||
class DetailView(views.DetailView):
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Instances" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("All Instances") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n sizeformat %}
|
||||
{% block title %}{% trans "Usage Overview" %}{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Overview") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{% if monitoring %}
|
||||
<div id="monitoring">
|
||||
<h3>{% trans "Monitoring" %}: </h3>
|
||||
|
@ -20,13 +20,12 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -50,10 +49,9 @@ class AddUser(forms.SelfHandlingForm):
|
||||
data['user_id'],
|
||||
data['role_id'])
|
||||
messages.success(request, _('Successfully added user to project.'))
|
||||
return True
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to add user to project.'))
|
||||
return shortcuts.redirect('horizon:syspanel:projects:users',
|
||||
tenant_id=data['tenant_id'])
|
||||
|
||||
|
||||
class CreateTenant(forms.SelfHandlingForm):
|
||||
@ -68,16 +66,16 @@ class CreateTenant(forms.SelfHandlingForm):
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.info('Creating project with name "%s"' % data['name'])
|
||||
api.tenant_create(request,
|
||||
project = api.tenant_create(request,
|
||||
data['name'],
|
||||
data['description'],
|
||||
data['enabled'])
|
||||
messages.success(request,
|
||||
_('%s was successfully created.')
|
||||
% data['name'])
|
||||
return project
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to create project.'))
|
||||
return shortcuts.redirect('horizon:syspanel:projects:index')
|
||||
|
||||
|
||||
class UpdateTenant(forms.SelfHandlingForm):
|
||||
@ -92,7 +90,7 @@ class UpdateTenant(forms.SelfHandlingForm):
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.info('Updating project with id "%s"' % data['id'])
|
||||
api.tenant_update(request,
|
||||
project = api.tenant_update(request,
|
||||
data['id'],
|
||||
data['name'],
|
||||
data['description'],
|
||||
@ -100,9 +98,9 @@ class UpdateTenant(forms.SelfHandlingForm):
|
||||
messages.success(request,
|
||||
_('%s was successfully updated.')
|
||||
% data['name'])
|
||||
return project
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to update project.'))
|
||||
return shortcuts.redirect('horizon:syspanel:projects:index')
|
||||
|
||||
|
||||
class UpdateQuotas(forms.SelfHandlingForm):
|
||||
@ -136,6 +134,6 @@ class UpdateQuotas(forms.SelfHandlingForm):
|
||||
messages.success(request,
|
||||
_('Quotas for %s were successfully updated.')
|
||||
% data['tenant_id'])
|
||||
return True
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to update quotas.'))
|
||||
return shortcuts.redirect('horizon:syspanel:projects:index')
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Add User To Project" %}{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Add User To Project") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{% include 'syspanel/projects/_add_user.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Create Project{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Project") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{% include 'syspanel/projects/_create.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Projects{% endblock %}
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Projects") refresh_link=refresh_link searchable="true" %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Modify Project Quotas{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Update Project") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{% include 'syspanel/projects/_quotas.html' with form=form %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Update Project{% endblock %}
|
||||
|
||||
@ -6,6 +6,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Update Project") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{% include 'syspanel/projects/_update.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n sizeformat %}
|
||||
{% block title %}{% trans "Project Usage Overview" %}{% endblock %}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{% include "horizon/common/_usage_summary.html" %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Project Users{% endblock %}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
<div id="tenant_users_table">
|
||||
{{ tenant_users_table.render }}
|
||||
</div>
|
||||
|
@ -51,8 +51,6 @@ class TenantsViewTests(test.BaseAdminViewTests):
|
||||
self.mox.StubOutWithMock(api.keystone, 'tenant_get')
|
||||
self.mox.StubOutWithMock(api.nova, 'tenant_quota_get')
|
||||
self.mox.StubOutWithMock(api.nova, 'tenant_quota_update')
|
||||
api.keystone.tenant_get(IgnoreArg(), tenant.id, admin=True) \
|
||||
.AndReturn(tenant)
|
||||
api.nova.tenant_quota_get(IgnoreArg(), tenant.id).AndReturn(quota)
|
||||
api.nova.tenant_quota_update(IgnoreArg(), tenant.id, **quota_data)
|
||||
self.mox.ReplayAll()
|
||||
|
@ -21,7 +21,7 @@
|
||||
import logging
|
||||
import operator
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
@ -36,6 +36,27 @@ from .tables import TenantsTable, TenantUsersTable, AddUsersTable
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TenantContextMixin(object):
|
||||
def get_object(self):
|
||||
if not hasattr(self, "_object"):
|
||||
tenant_id = self.kwargs['tenant_id']
|
||||
try:
|
||||
self._object = api.keystone.tenant_get(self.request,
|
||||
tenant_id,
|
||||
admin=True)
|
||||
except:
|
||||
redirect = reverse("horizon:syspanel:projects:index")
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve project information.'),
|
||||
redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(TenantContextMixin, self).get_context_data(**kwargs)
|
||||
context['tenant'] = self.get_object()
|
||||
return context
|
||||
|
||||
|
||||
class IndexView(tables.DataTableView):
|
||||
table_class = TenantsTable
|
||||
template_name = 'syspanel/projects/index.html'
|
||||
@ -54,28 +75,20 @@ class IndexView(tables.DataTableView):
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateTenant
|
||||
template_name = 'syspanel/projects/create.html'
|
||||
success_url = reverse_lazy('horizon:syspanel:projects:index')
|
||||
|
||||
|
||||
class UpdateView(forms.ModalFormView):
|
||||
class UpdateView(TenantContextMixin, forms.ModalFormView):
|
||||
form_class = UpdateTenant
|
||||
template_name = 'syspanel/projects/update.html'
|
||||
context_object_name = 'tenant'
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
tenant_id = kwargs['tenant_id']
|
||||
try:
|
||||
return api.keystone.tenant_get(self.request, tenant_id, admin=True)
|
||||
except:
|
||||
redirect = reverse("horizon:syspanel:projects:index")
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve project.'),
|
||||
redirect=redirect)
|
||||
success_url = reverse_lazy('horizon:syspanel:projects:index')
|
||||
|
||||
def get_initial(self):
|
||||
return {'id': self.object.id,
|
||||
'name': self.object.name,
|
||||
'description': getattr(self.object, "description", ""),
|
||||
'enabled': self.object.enabled}
|
||||
project = self.get_object()
|
||||
return {'id': project.id,
|
||||
'name': project.name,
|
||||
'description': project.description,
|
||||
'enabled': project.enabled}
|
||||
|
||||
|
||||
class UsersView(tables.MultiTableView):
|
||||
@ -116,15 +129,14 @@ class UsersView(tables.MultiTableView):
|
||||
return context
|
||||
|
||||
|
||||
class AddUserView(forms.ModalFormView):
|
||||
class AddUserView(TenantContextMixin, forms.ModalFormView):
|
||||
form_class = AddUser
|
||||
template_name = 'syspanel/projects/add_user.html'
|
||||
context_object_name = 'tenant'
|
||||
success_url = 'horizon:syspanel:projects:users'
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
return api.keystone.tenant_get(self.request,
|
||||
kwargs["tenant_id"],
|
||||
admin=True)
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.request.POST['tenant_id'],))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(AddUserView, self).get_context_data(**kwargs)
|
||||
@ -153,19 +165,19 @@ class AddUserView(forms.ModalFormView):
|
||||
'role_id': getattr(default_role, "id", None)}
|
||||
|
||||
|
||||
class QuotasView(forms.ModalFormView):
|
||||
class QuotasView(TenantContextMixin, forms.ModalFormView):
|
||||
form_class = UpdateQuotas
|
||||
template_name = 'syspanel/projects/quotas.html'
|
||||
context_object_name = 'tenant'
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
return api.keystone.tenant_get(self.request,
|
||||
kwargs["tenant_id"],
|
||||
admin=True)
|
||||
success_url = reverse_lazy('horizon:syspanel:projects:index')
|
||||
|
||||
def get_initial(self):
|
||||
try:
|
||||
quotas = api.nova.tenant_quota_get(self.request,
|
||||
self.kwargs['tenant_id'])
|
||||
except:
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to retrieve quota information."),
|
||||
redirect=reverse(self.get_sucess_url))
|
||||
return {
|
||||
'tenant_id': self.kwargs['tenant_id'],
|
||||
'metadata_items': quotas.metadata_items,
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Quotas{% endblock %}
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Default Quotas") refresh_link=refresh_link searchable="true" %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Services{% endblock %}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Services") refresh_link=refresh_link searchable="true" %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -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 %}
|
@ -20,8 +20,6 @@
|
||||
|
||||
import logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.forms import ValidationError
|
||||
from django.utils.translation import force_unicode, ugettext_lazy as _
|
||||
from django.views.decorators.debug import sensitive_variables
|
||||
@ -29,6 +27,7 @@ from django.views.decorators.debug import sensitive_variables
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from horizon.utils import validators
|
||||
|
||||
|
||||
@ -37,7 +36,7 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
class BaseUserForm(forms.SelfHandlingForm):
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(BaseUserForm, self).__init__(*args, **kwargs)
|
||||
super(BaseUserForm, self).__init__(request, *args, **kwargs)
|
||||
# Populate tenant choices
|
||||
tenant_choices = [('', _("Select a project"))]
|
||||
|
||||
@ -46,10 +45,6 @@ class BaseUserForm(forms.SelfHandlingForm):
|
||||
tenant_choices.append((tenant.id, tenant.name))
|
||||
self.fields['tenant_id'].choices = tenant_choices
|
||||
|
||||
@classmethod
|
||||
def _instantiate(cls, request, *args, **kwargs):
|
||||
return cls(request, *args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
'''Check to make sure password fields match.'''
|
||||
data = super(forms.Form, self).clean()
|
||||
@ -103,10 +98,9 @@ class CreateUserForm(BaseUserForm):
|
||||
except:
|
||||
exceptions.handle(request,
|
||||
_('Unable to add user to primary project.'))
|
||||
return shortcuts.redirect('horizon:syspanel:users:index')
|
||||
return new_user
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to create user.'))
|
||||
return shortcuts.redirect('horizon:syspanel:users:index')
|
||||
|
||||
|
||||
class UpdateUserForm(BaseUserForm):
|
||||
@ -140,7 +134,6 @@ class UpdateUserForm(BaseUserForm):
|
||||
user_is_editable = api.keystone_can_edit_user()
|
||||
user = data.pop('id')
|
||||
tenant = data.pop('tenant_id')
|
||||
data.pop('method')
|
||||
|
||||
if user_is_editable:
|
||||
password = data.pop('password')
|
||||
@ -184,4 +177,4 @@ class UpdateUserForm(BaseUserForm):
|
||||
messages.error(request,
|
||||
_('Unable to update %(attributes)s for the user.')
|
||||
% {"attributes": ", ".join(failed)})
|
||||
return shortcuts.redirect('horizon:syspanel:users:index')
|
||||
return True
|
||||
|
@ -1,9 +1,9 @@
|
||||
import logging
|
||||
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import messages
|
||||
from horizon import tables
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Create User{% endblock %}
|
||||
|
||||
@ -7,6 +7,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create User") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{% include 'syspanel/users/_create.html' %}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Users{% endblock %}
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Users") refresh_link=refresh_link searchable="true" %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Update User{% endblock %}
|
||||
|
||||
@ -7,6 +7,6 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Update User") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{% include 'syspanel/users/_update.html' %}
|
||||
{% endblock %}
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
import operator
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.decorators.debug import sensitive_post_parameters
|
||||
@ -50,33 +50,43 @@ class IndexView(tables.DataTableView):
|
||||
class UpdateView(forms.ModalFormView):
|
||||
form_class = UpdateUserForm
|
||||
template_name = 'syspanel/users/update.html'
|
||||
context_object_name = 'user'
|
||||
success_url = reverse_lazy('horizon:syspanel:users:index')
|
||||
|
||||
@method_decorator(sensitive_post_parameters('password',
|
||||
'confirm_password'))
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(UpdateView, self).dispatch(*args, **kwargs)
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
user_id = kwargs['user_id']
|
||||
def get_object(self):
|
||||
if not hasattr(self, "_object"):
|
||||
try:
|
||||
return api.user_get(self.request, user_id, admin=True)
|
||||
self._object = api.user_get(self.request,
|
||||
self.kwargs['user_id'],
|
||||
admin=True)
|
||||
except:
|
||||
redirect = reverse("horizon:syspanel:users:index")
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to update user.'),
|
||||
redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
context['user'] = self.get_object()
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
return {'id': self.object.id,
|
||||
'name': getattr(self.object, 'name', None),
|
||||
'tenant_id': getattr(self.object, 'tenantId', None),
|
||||
'email': getattr(self.object, 'email', '')}
|
||||
user = self.get_object()
|
||||
return {'id': user.id,
|
||||
'name': user.name,
|
||||
'tenant_id': getattr(user, 'tenantId', None),
|
||||
'email': user.email}
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateUserForm
|
||||
template_name = 'syspanel/users/create.html'
|
||||
success_url = reverse_lazy('horizon:syspanel:users:index')
|
||||
|
||||
@method_decorator(sensitive_post_parameters('password',
|
||||
'confirm_password'))
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Volume Details" %}{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Volume Detail") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
{{ tab_group.render }}
|
||||
|
@ -1,4 +1,4 @@
|
||||
{% extends 'syspanel/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Volumes" %}{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
{% include "horizon/common/_page_header.html" with title=_("Volumes") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block syspanel_main %}
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -22,3 +22,4 @@ from django.forms import widgets
|
||||
# Convenience imports for public API components.
|
||||
from .base import SelfHandlingForm, DateForm
|
||||
from .views import ModalFormView
|
||||
from .fields import DynamicTypedChoiceField, DynamicChoiceField
|
||||
|
@ -18,41 +18,22 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django import forms
|
||||
from django.forms.forms import NON_FIELD_ERRORS
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils import dates, timezone
|
||||
|
||||
from horizon import exceptions
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SelfHandlingForm(forms.Form):
|
||||
"""
|
||||
A base :class:`Form <django:django.forms.Form>` class which includes
|
||||
processing logic in its subclasses and handling errors raised during
|
||||
form processing.
|
||||
|
||||
.. attribute:: method
|
||||
|
||||
A :class:`CharField <django:django.forms.CharField>` instance
|
||||
rendered with a
|
||||
:class:`CharField <django:django.forms.widgets.HiddenInput>`
|
||||
widget which is automatically set to the value of the class name.
|
||||
|
||||
This is used to determine whether this form should handle the
|
||||
input it is given or not.
|
||||
processing logic in its subclasses.
|
||||
"""
|
||||
method = forms.CharField(required=True, widget=forms.HiddenInput)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
initial = kwargs.pop('initial', {})
|
||||
initial['method'] = self.__class__.__name__
|
||||
kwargs['initial'] = initial
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
self.request = request
|
||||
if not hasattr(self, "handle"):
|
||||
raise NotImplementedError("%s does not define a handle method."
|
||||
% self.__class__.__name__)
|
||||
super(SelfHandlingForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def api_error(self, message):
|
||||
@ -64,52 +45,6 @@ class SelfHandlingForm(forms.Form):
|
||||
"""
|
||||
self._errors[NON_FIELD_ERRORS] = self.error_class([message])
|
||||
|
||||
def get_success_url(self, request=None):
|
||||
"""
|
||||
Returns the URL to redirect to after a successful handling.
|
||||
"""
|
||||
if self.completion_view:
|
||||
return reverse(self.completion_view)
|
||||
if self.completion_url:
|
||||
return self.completion_url
|
||||
return request.get_full_path()
|
||||
|
||||
@classmethod
|
||||
def _instantiate(cls, request, *args, **kwargs):
|
||||
""" Instantiates the form. Allows customization in subclasses. """
|
||||
return cls(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def maybe_handle(cls, request, *args, **kwargs):
|
||||
"""
|
||||
If the form is valid,
|
||||
:meth:`~horizon.forms.SelfHandlingForm.maybe_handle` calls a
|
||||
``handle(request, data)`` method on its subclass to
|
||||
determine what action to take.
|
||||
|
||||
Any exceptions raised during processing are captured and
|
||||
converted to messages.
|
||||
"""
|
||||
|
||||
if request.method != 'POST' or \
|
||||
cls.__name__ != request.POST.get('method'):
|
||||
return cls._instantiate(request, *args, **kwargs), None
|
||||
|
||||
if request.FILES:
|
||||
form = cls._instantiate(request, request.POST, request.FILES,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
form = cls._instantiate(request, request.POST, *args, **kwargs)
|
||||
|
||||
if not form.is_valid():
|
||||
return form, None
|
||||
|
||||
try:
|
||||
return form, form.handle(request, form.cleaned_data)
|
||||
except:
|
||||
exceptions.handle(request)
|
||||
return form, None
|
||||
|
||||
|
||||
class DateForm(forms.Form):
|
||||
""" A simple form for selecting a start date. """
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user