Commit 213d4de9 by Őry Máté

Merge branch 'feature-save-as-naming' into 'master'

Fix save_as issues

* remove template global unique constaint (fixes #147)
* allow changing name before starting save_as operation
parents 73c6938b a7af3371
{% extends "dashboard/operate.html" %}
{% load i18n %}
{% load sizefieldtags %}
{% block question %}
<p>
{% blocktrans %}
Choose a name for the new template.
{% endblocktrans %}
</p>
<p class="text-info">{{op.name}}: {{op.description}}</p>
{% endblock %}
{% block formfields %}
<div class="form-group">
<label for="input-name" class="col-sm-4">{% trans "Name of template" %}</label>
<div class="col-sm-8">
<input type="text" value="{{name}}" name="name" class="form-control" />
</div>
</div>
{% endblock %}
...@@ -112,6 +112,73 @@ class VmOperationViewTestCase(unittest.TestCase): ...@@ -112,6 +112,73 @@ class VmOperationViewTestCase(unittest.TestCase):
self.assertEquals( self.assertEquals(
view.as_view()(request, pk=1234).render().status_code, 200) view.as_view()(request, pk=1234).render().status_code, 200)
def test_save_as_wo_name(self):
request = FakeRequestFactory(POST={})
view = vm_ops['save_as_template']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.save_as_template = Instance._ops['save_as_template'](inst)
inst.save_as_template.async = MagicMock()
inst.has_level.return_value = True
go.return_value = inst
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)['location']
assert not msg.error.called
def test_save_as_w_name(self):
request = FakeRequestFactory(POST={'name': 'foobar'})
view = vm_ops['save_as_template']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.save_as_template = Instance._ops['save_as_template'](inst)
inst.save_as_template.async = MagicMock()
inst.has_level.return_value = True
go.return_value = inst
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)['location']
assert not msg.error.called
def test_save_as_failed(self):
request = FakeRequestFactory(POST={})
view = vm_ops['save_as_template']
with patch.object(view, 'get_object') as go, \
patch('dashboard.views.messages') as msg, \
patch('dashboard.views.get_object_or_404') as go4:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.save_as_template = Instance._ops['save_as_template'](inst)
inst.save_as_template.async = MagicMock()
inst.save_as_template.async.side_effect = Exception
inst.has_level.return_value = True
go.return_value = inst
go4.return_value = MagicMock()
assert view.as_view()(request, pk=1234)['location']
assert msg.error.called
def test_save_as_template(self):
request = FakeRequestFactory()
view = vm_ops['save_as_template']
with patch.object(view, 'get_object') as go:
inst = MagicMock(spec=Instance)
inst._meta.object_name = "Instance"
inst.name = 'foo'
inst.save_as_template = Instance._ops['save_as_template'](inst)
inst.has_level.return_value = True
go.return_value = inst
rend = view.as_view()(request, pk=1234).render()
self.assertEquals(rend.status_code, 200)
assert 'foo v1' in rend.content
def FakeRequestFactory(*args, **kwargs): def FakeRequestFactory(*args, **kwargs):
''' FakeRequestFactory, FakeMessages and FakeRequestContext are good for ''' FakeRequestFactory, FakeMessages and FakeRequestContext are good for
......
...@@ -513,7 +513,7 @@ class VmMigrateView(VmOperationView): ...@@ -513,7 +513,7 @@ class VmMigrateView(VmOperationView):
template_name = 'dashboard/_vm-migrate.html' template_name = 'dashboard/_vm-migrate.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super(VmOperationView, self).get_context_data(**kwargs) ctx = super(VmMigrateView, self).get_context_data(**kwargs)
ctx['nodes'] = [n for n in Node.objects.filter(enabled=True) ctx['nodes'] = [n for n in Node.objects.filter(enabled=True)
if n.state == "ONLINE"] if n.state == "ONLINE"]
return ctx return ctx
...@@ -528,6 +528,26 @@ class VmMigrateView(VmOperationView): ...@@ -528,6 +528,26 @@ class VmMigrateView(VmOperationView):
return super(VmMigrateView, self).post(request, extra, *args, **kwargs) return super(VmMigrateView, self).post(request, extra, *args, **kwargs)
class VmSaveView(VmOperationView):
op = 'save_as_template'
icon = 'save'
template_name = 'dashboard/_vm-save.html'
def get_context_data(self, **kwargs):
ctx = super(VmSaveView, self).get_context_data(**kwargs)
ctx['name'] = self.get_op()._rename(self.object.name)
return ctx
def post(self, request, extra=None, *args, **kwargs):
if extra is None:
extra = {}
name = self.request.POST.get("name")
if name:
extra["name"] = name
return super(VmSaveView, self).post(request, extra, *args, **kwargs)
vm_ops = { vm_ops = {
'reset': VmOperationView.factory(op='reset', icon='bolt'), 'reset': VmOperationView.factory(op='reset', icon='bolt'),
'deploy': VmOperationView.factory(op='deploy', icon='play'), 'deploy': VmOperationView.factory(op='deploy', icon='play'),
...@@ -535,8 +555,7 @@ vm_ops = { ...@@ -535,8 +555,7 @@ vm_ops = {
'reboot': VmOperationView.factory(op='reboot', icon='refresh'), 'reboot': VmOperationView.factory(op='reboot', icon='refresh'),
'shut_off': VmOperationView.factory(op='shut_off', icon='ban-circle'), 'shut_off': VmOperationView.factory(op='shut_off', icon='ban-circle'),
'shutdown': VmOperationView.factory(op='shutdown', icon='off'), 'shutdown': VmOperationView.factory(op='shutdown', icon='off'),
'save_as_template': VmOperationView.factory( 'save_as_template': VmSaveView,
op='save_as_template', icon='save'),
'destroy': VmOperationView.factory(op='destroy', icon='remove'), 'destroy': VmOperationView.factory(op='destroy', icon='remove'),
'sleep': VmOperationView.factory(op='sleep', icon='moon'), 'sleep': VmOperationView.factory(op='sleep', icon='moon'),
'wake_up': VmOperationView.factory(op='wake_up', icon='sun'), 'wake_up': VmOperationView.factory(op='wake_up', icon='sun'),
......
...@@ -110,8 +110,7 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel): ...@@ -110,8 +110,7 @@ class InstanceTemplate(AclBase, VirtualMachineDescModel, TimeStampedModel):
('operator', _('operator')), ('operator', _('operator')),
('owner', _('owner')), # superuser, can delete, delegate perms ('owner', _('owner')), # superuser, can delete, delegate perms
) )
name = CharField(max_length=100, unique=True, name = CharField(max_length=100, verbose_name=_('name'),
verbose_name=_('name'),
help_text=_('Human readable name of template.')) help_text=_('Human readable name of template.'))
description = TextField(verbose_name=_('description'), blank=True) description = TextField(verbose_name=_('description'), blank=True)
parent = ForeignKey('self', null=True, blank=True, parent = ForeignKey('self', null=True, blank=True,
......
...@@ -289,7 +289,7 @@ class SaveAsTemplateOperation(InstanceOperation): ...@@ -289,7 +289,7 @@ class SaveAsTemplateOperation(InstanceOperation):
v = 1 v = 1
return "%s v%d" % (name, v) return "%s v%d" % (name, v)
def _operation(self, activity, user, system, timeout=300, def _operation(self, activity, user, system, timeout=300, name=None,
with_shutdown=True, **kwargs): with_shutdown=True, **kwargs):
if with_shutdown: if with_shutdown:
try: try:
...@@ -306,7 +306,7 @@ class SaveAsTemplateOperation(InstanceOperation): ...@@ -306,7 +306,7 @@ class SaveAsTemplateOperation(InstanceOperation):
'description': self.instance.description, 'description': self.instance.description,
'lease': self.instance.lease, # Can be problem in new VM 'lease': self.instance.lease, # Can be problem in new VM
'max_ram_size': self.instance.max_ram_size, 'max_ram_size': self.instance.max_ram_size,
'name': self._rename(self.instance.name), 'name': name or self._rename(self.instance.name),
'num_cores': self.instance.num_cores, 'num_cores': self.instance.num_cores,
'owner': user, 'owner': user,
'parent': self.instance.template, # Can be problem 'parent': self.instance.template, # Can be problem
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment