From 6efe1ef5a1c8783b056e5e5a7cf55e76e340a3d4 Mon Sep 17 00:00:00 2001
From: Őry Máté <orymate@iit.bme.hu>
Date: Sat, 9 Feb 2013 18:51:44 +0100
Subject: [PATCH] one: add save as functionality

---
 cloud/urls.py                                              |   2 ++
 one/admin.py                                               |   1 +
 one/migrations/0009_auto__add_field_instancetype_credit.py | 235 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 one/models.py                                              |  27 ++++++++++++++++++---------
 one/static/base.less                                       |  26 ++++++++++++++++++++++++++
 one/static/box.less                                        |  21 +++++++++++++++++++++
 one/templates/home.html                                    |   3 +++
 one/templates/new-template-flow-1.html                     |  60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 one/templates/new-template-flow.html                       | 167 +++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------
 one/templates/show.html                                    |  23 +++++++++++++++++++++++
 one/views.py                                               |  73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
 11 files changed, 493 insertions(+), 145 deletions(-)
 create mode 100644 one/migrations/0009_auto__add_field_instancetype_credit.py
 create mode 100644 one/templates/new-template-flow-1.html

diff --git a/cloud/urls.py b/cloud/urls.py
index d8f653d..491e9a1 100644
--- a/cloud/urls.py
+++ b/cloud/urls.py
@@ -26,12 +26,14 @@ urlpatterns = patterns('',
      url(r'^vm/restart/(?P<iid>\d+)/$', 'one.views.vm_restart', name='vm_restart'),
      url(r'^vm/port_add/(?P<iid>\d+)/$', 'one.views.vm_port_add', name='vm_port_add'),
      url(r'^vm/port_del/(?P<iid>\d+)/(?P<proto>tcp|udp)/(?P<public>\d+)/$', 'one.views.vm_port_del', name='vm_port_del'),
+     url(r'^vm/saveas/(?P<vmid>\d+)$', 'one.views.vm_saveas', name='vm_saveas'),
      url(r'^reload/$', 'firewall.views.reload_firewall', name='reload_firewall'),
      url(r'^fwapi/$', 'firewall.views.firewall_api', name='firewall_api'),
      url(r'^store/$', 'store.views.index', name='store_index'),
      url(r'^store/gui/$', 'store.views.gui', name='store_gui'),
      url(r'^store/top/$', 'store.views.toplist', name='store_top'),
      url(r'^ajax/templateWizard$', 'one.views.ajax_template_wizard', name='ajax_template_wizard'),
+     url(r'^ajax/template_name_unique/(?P<name>.*)$', 'one.views.ajax_template_name_unique', name='ajax_template_name_unique'),
      url(r'^ajax/store/list$', 'store.views.ajax_listfolder', name='store_ajax_listfolder'),
      url(r'^ajax/store/download$', 'store.views.ajax_download', name='store_ajax_download'),
      url(r'^ajax/store/upload$', 'store.views.ajax_upload', name='store_ajax_upload'),
diff --git a/one/admin.py b/one/admin.py
index 1df5ec4..603fa64 100644
--- a/one/admin.py
+++ b/one/admin.py
@@ -47,6 +47,7 @@ submit_vm.short_description = _('Submit VM')
 
 class TemplateAdmin(contrib.admin.ModelAdmin):
     model=models.Template
+    list_display = ('name', 'state', 'owner', 'system')
 
 class InstanceAdmin(contrib.admin.ModelAdmin):
     model=models.Instance
diff --git a/one/migrations/0009_auto__add_field_instancetype_credit.py b/one/migrations/0009_auto__add_field_instancetype_credit.py
new file mode 100644
index 0000000..e845732
--- /dev/null
+++ b/one/migrations/0009_auto__add_field_instancetype_credit.py
@@ -0,0 +1,235 @@
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding field 'InstanceType.credit'
+        db.add_column('one_instancetype', 'credit',
+                      self.gf('django.db.models.fields.IntegerField')(default=1),
+                      keep_default=False)
+
+
+    def backwards(self, orm):
+        # Deleting field 'InstanceType.credit'
+        db.delete_column('one_instancetype', 'credit')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'firewall.domain': {
+            'Meta': {'object_name': 'Domain'},
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'ttl': ('django.db.models.fields.IntegerField', [], {'default': '600'})
+        },
+        'firewall.group': {
+            'Meta': {'object_name': 'Group'},
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'firewall.host': {
+            'Meta': {'object_name': 'Host'},
+            'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['firewall.Group']", 'null': 'True', 'blank': 'True'}),
+            'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ipv4': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
+            'ipv6': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'location': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'mac': ('firewall.fields.MACAddressField', [], {'unique': 'True', 'max_length': '17'}),
+            'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'pub_ipv4': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
+            'reverse': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+            'shared_ip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'vlan': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['firewall.Vlan']"})
+        },
+        'firewall.vlan': {
+            'Meta': {'object_name': 'Vlan'},
+            'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'dhcp_pool': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['firewall.Domain']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'interface': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
+            'ipv4': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
+            'ipv6': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
+            'modified_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
+            'net4': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
+            'net6': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'prefix4': ('django.db.models.fields.IntegerField', [], {'default': '16'}),
+            'prefix6': ('django.db.models.fields.IntegerField', [], {'default': '80'}),
+            'reverse_domain': ('django.db.models.fields.TextField', [], {}),
+            'snat_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}),
+            'snat_to': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['firewall.Vlan']", 'null': 'True', 'blank': 'True'}),
+            'vid': ('django.db.models.fields.IntegerField', [], {'unique': 'True'})
+        },
+        'one.disk': {
+            'Meta': {'ordering': "['name']", 'object_name': 'Disk'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'one.instance': {
+            'Meta': {'object_name': 'Instance'},
+            'active_since': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'firewall_host': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['firewall.Host']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'one_id': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'pw': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'share': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.Share']", 'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.CharField', [], {'default': "'DEPLOYABLE'", 'max_length': '20'}),
+            'template': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.Template']"}),
+            'time_of_delete': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'time_of_suspend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'})
+        },
+        'one.instancetype': {
+            'CPU': ('django.db.models.fields.IntegerField', [], {}),
+            'Meta': {'object_name': 'InstanceType'},
+            'RAM': ('django.db.models.fields.IntegerField', [], {}),
+            'credit': ('django.db.models.fields.IntegerField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'one.network': {
+            'Meta': {'ordering': "['name']", 'object_name': 'Network'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'nat': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+        },
+        'one.share': {
+            'Meta': {'object_name': 'Share'},
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['school.Group']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'instance_limit': ('django.db.models.fields.IntegerField', [], {}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'per_user_limit': ('django.db.models.fields.IntegerField', [], {}),
+            'template': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.Template']"}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '10'})
+        },
+        'one.sshkey': {
+            'Meta': {'object_name': 'SshKey'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '2000'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'one.template': {
+            'Meta': {'object_name': 'Template'},
+            'access_type': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'disk': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.Disk']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'instance_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.InstanceType']"}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'network': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.Network']"}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'state': ('django.db.models.fields.CharField', [], {'default': "'NEW'", 'max_length': '10'}),
+            'system': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        'one.userclouddetails': {
+            'Meta': {'object_name': 'UserCloudDetails'},
+            'disk_quota': ('django.db.models.fields.IntegerField', [], {'default': '2048'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'instance_quota': ('django.db.models.fields.IntegerField', [], {'default': '20'}),
+            'share_quota': ('django.db.models.fields.IntegerField', [], {'default': '100'}),
+            'smb_password': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
+            'ssh_key': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['one.SshKey']", 'null': 'True'}),
+            'ssh_private_key': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
+        },
+        'school.course': {
+            'Meta': {'object_name': 'Course'},
+            'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
+            'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_group_of'", 'null': 'True', 'to': "orm['school.Group']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'null': 'True', 'blank': 'True'}),
+            'owners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['school.Person']", 'null': 'True', 'blank': 'True'}),
+            'short_name': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'})
+        },
+        'school.group': {
+            'Meta': {'unique_together': "(('name', 'course', 'semester'),)", 'object_name': 'Group'},
+            'course': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['school.Course']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'course_groups'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['school.Person']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'owners': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'owned_groups'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['school.Person']"}),
+            'semester': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['school.Semester']"})
+        },
+        'school.person': {
+            'Meta': {'object_name': 'Person'},
+            'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'default': "'hu'", 'max_length': '10'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
+        },
+        'school.semester': {
+            'Meta': {'object_name': 'Semester'},
+            'end': ('django.db.models.fields.DateField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20'}),
+            'start': ('django.db.models.fields.DateField', [], {})
+        }
+    }
+
+    complete_apps = ['one']
\ No newline at end of file
diff --git a/one/models.py b/one/models.py
index 4aca64c..b0e08aa 100644
--- a/one/models.py
+++ b/one/models.py
@@ -122,10 +122,11 @@ class SshKey(models.Model):
 TEMPLATE_STATES = (("INIT", _('init')), ("PREP", _('perparing')), ("SAVE", _('saving')), ("READY", _('ready')))
 
 
-TYPES = {"LAB": {"verbose_name": _('lab'),         "suspend": td(hours=5),  "delete": td(days=15)},
-         "PROJECT": {"verbose_name": _('project'), "suspend": td(weeks=5),  "delete": td(days=366/2)},
-         "SERVER": {"verbose_name": _('server'),   "suspend": td(days=365), "delete": None},
+TYPES = {"LAB": {"verbose_name": _('lab'),         "id": "LAB",     "suspend": td(hours=5),  "delete": td(days=15),    "help_text": _('For lab or home work with short life time.')},
+         "PROJECT": {"verbose_name": _('project'), "id": "PROJECT", "suspend": td(weeks=5),  "delete": td(days=366/2), "help_text": _('For project work.')},
+         "SERVER": {"verbose_name": _('server'),   "id": "SERVER",  "suspend": td(days=365), "delete": None,           "help_text": _('For long term server use.')},
          }
+TYPES_L = sorted(TYPES.values(), key=lambda m: m["suspend"])
 TYPES_C = tuple([(i[0], i[1]["verbose_name"]) for i in TYPES.items()])
 class Share(models.Model):
     name = models.CharField(max_length=100, unique=True, verbose_name=_('name'))
@@ -222,10 +223,14 @@ class InstanceType(models.Model):
             verbose_name=_('name'))
     CPU = models.IntegerField(help_text=_('CPU cores.'))
     RAM = models.IntegerField(help_text=_('Mebibytes of memory.'))
+    credit = models.IntegerField(verbose_name=_('credits'),
+            help_text=_('Price of instance.'))
     def __unicode__(self):
         return u"%s" % self.name
+    class Meta:
+        ordering = ['credit']
 
-TEMPLATE_STATES = (('NEW', _('new')), ('PREPARING', _('preparing')),
+TEMPLATE_STATES = (('NEW', _('new')), 
                    ('SAVING', _('saving')), ('READY', _('ready')), )
 """
 Virtual machine template specifying OS, disk, type and network.
@@ -455,7 +460,8 @@ class Instance(models.Model):
         proc = subprocess.Popen(["/opt/occi.sh", "compute",
                "delete", "%d"%self.one_id], stdout=subprocess.PIPE)
         (out, err) = proc.communicate()
-        self.firewall_host.delete()
+        if self.firewall_host:
+            self.firewall_host.delete()
         reload_firewall_lock()
 
     def _update_vm(self, template):
@@ -499,7 +505,10 @@ class Instance(models.Model):
         imgname = "template-%d-%d" % (self.template.id, self.id)
         self._update_vm('<DISK id="0"><SAVE_AS name="%s"/></DISK>' % imgname)
         self._change_state("SHUTDOWN")
-    def is_save_as_done(self):
+        t = self.template
+        t.state = 'SAVING'
+        t.save()
+    def check_if_is_save_as_done(self):
         self.update_state()
         if self.state != 'DONE':
             return false
@@ -509,9 +518,9 @@ class Instance(models.Model):
         if len(disks) != 1:
             return false
         self.template.disk_id = disks[0].id
-        self.template.status = 'READY'
+        self.template.state = 'READY'
         self.template.save()
-        self.host.delete()
+        self.firewall_host.delete()
         return True
 
 
@@ -523,7 +532,7 @@ def delete_instance(sender, instance, using, **kwargs):
     if instance.state != "DONE":
         instance.one_delete()
     try:
-        instance.host.delete()
+        instance.firewall_host.delete()
     except:
         pass
 post_delete.connect(delete_instance, sender=Instance, dispatch_uid="delete_instance")
diff --git a/one/static/base.less b/one/static/base.less
index 35e81a9..4f05340 100644
--- a/one/static/base.less
+++ b/one/static/base.less
@@ -43,6 +43,21 @@ body
         margin-top:0;
         padding:10px;
     }
+    &.wide {
+        margin-right: 30px;
+    }
+    &.note {
+        background-color: #ffc;
+    }
+    ol {
+        margin-left: 2em;
+        &.done {
+            text-color: #aaa;
+        }
+    }
+}
+.big {
+    font-size: 2em;
 }
 .wm-list{
     list-style: none;
@@ -75,6 +90,14 @@ ul.messagelist li.error
 {
     background-image:url(admin/img/icon_error.gif);
 }
+input.validated {
+    padding-right: 15px;
+    &.error
+    {
+        background:#fcc url(admin/img/icon_error.gif) right center no-repeat;
+        padding-right: 15px;
+    }
+}
 
 .errornote
 {
@@ -187,3 +210,6 @@ textarea {
         box-shadow: inset 0 0 10px rgba(0,0,0,0.2), 0 0 10px rgba(255,255,0,0.8);
     }
 }
+.hilight {
+    background-color: #ff6;
+}
diff --git a/one/static/box.less b/one/static/box.less
index 7bda568..8ca9cfe 100644
--- a/one/static/box.less
+++ b/one/static/box.less
@@ -282,6 +282,27 @@
         background-image: url(icons/computer--plus.png);
     }
 }
+#template-wizard {
+
+    .size-summary {
+        font-size: .8em;
+        text-align: right;
+        span {
+            background-position: left center;
+            background-repeat: no-repeat;
+            padding-left: 18px;
+        }
+        .cpu{
+            background-image: url(icons/processor.png)
+        }
+        .memory{
+            background-image: url(icons/memory.png)
+        }
+        .credit{
+            background-image: url(icons/documents-stack.png) /* TODO */
+        }
+    }
+}
 
 .file-list{
     list-style: none;
diff --git a/one/templates/home.html b/one/templates/home.html
index 934be3f..4e90499 100644
--- a/one/templates/home.html
+++ b/one/templates/home.html
@@ -73,6 +73,9 @@
     <div class="contentblock" id="template">
         <h2>{% trans "My templates" %}</h2>
         <ul class="wm-list">
+            {% for t in mytemplates %}
+            <li class="wm">{{t.name}}</li>
+            {% endfor %}
             <li class="wm">
                 <div class="summary">
                     <div class="quota">
diff --git a/one/templates/new-template-flow-1.html b/one/templates/new-template-flow-1.html
new file mode 100644
index 0000000..f5abce5
--- /dev/null
+++ b/one/templates/new-template-flow-1.html
@@ -0,0 +1,60 @@
+{% load i18n %}
+{% get_current_language as LANGUAGE_CODE %}
+<form action="/ajax/templateWizard" method="post" id="template-wizard">{% csrf_token %}
+    <div id="new-template-step-1" class="wizard">
+        <div class="progress">
+            <div class="bar-container">
+                <div class="bar" style="width: 33%"></div>
+            </div>
+            <h3>{% blocktrans with step=1 all=3 %}{{step}}/{{all}}{% endblocktrans %}</h3>
+        </div>
+        <h2>{% blocktrans with step=1 %}Step {{step}}{% endblocktrans %}</h2>
+        <p class="help">{% trans "Please choose the base system you want to customize." %}</p>
+        <div class="container">
+            <ul class="tpl-list modal">
+                {% if not templates %}
+                <p>{% trans "There are no available templates." %}</p>
+                {% endif %}
+                {% for m in templates %}
+                <li class="tpl">
+                    <div class="summary">
+                        <div class="name tpl os-{{m.os_type}}" title="{{m.description}}"><label><input type="radio" name="base" value="{{m.id}}" /> {{m.name}}</label></div>
+                        <div class="clear"></div>
+                    </div>
+                </li>
+                {% endfor %}
+            </ul>
+        </div>
+        <nav>
+            <a href="#" class="prev">{% trans "&laquo; Cancel" %}</a>
+            <input type="submit" class="next" value="{% trans "Next &raquo;" %}" />
+            <div class="clear"></div>
+        </nav>
+        <script type="text/javascript">
+            $(function(){
+                $('#template-wizard nav .prev').click(function(){
+                    $('#modal').hide();
+                })
+                $('#template-wizard').unbind('submit').submit(function(e){
+                    if ($("#template-wizard input[type=radio]:checked").length==0) {
+                        $("#template-wizard .help").addClass('hilight');
+                    }
+                    else {
+                        $.ajax({
+                            'type': 'POST',
+                            'url': '/ajax/templateWizard',
+                            'data': $('#template-wizard').serialize(),
+                            'success': function(data) {
+                              $('#modal-container').html(data);
+                            }
+                        });
+                    }
+                    e.preventDefault();
+                    e.stopPropagation();
+                    return false;
+                })
+            })
+        </script>
+    </div>
+    </div>
+</form>
diff --git a/one/templates/new-template-flow.html b/one/templates/new-template-flow.html
index 645307a..9d686f1 100644
--- a/one/templates/new-template-flow.html
+++ b/one/templates/new-template-flow.html
@@ -1,88 +1,7 @@
 {% load i18n %}
 {% get_current_language as LANGUAGE_CODE %}
-<form action="/" method="post" id="template-wizard">
-    <div id="new-template-step-1" class="wizard">
-        <div class="progress">
-            <div class="bar-container">
-                <div class="bar" style="width: 33%"></div>
-            </div>
-            <h3>{% blocktrans with step=1 all=3 %}{{step}}/{{all}}{% endblocktrans %}</h3>
-        </div>
-        <h2>{% blocktrans with step=1 %}Step {{step}}{% endblocktrans %}</h2>
-        <p>Leírás, mit is kéne itt ezen az ablakon csinálni, és miért jó, ha azt csinálja, amit.</p>
-        <div class="container">
-            <ul class="wm-list modal">
-                {% for m in templates %}
-                <li class="wm">
-                    <form method="POST" action="/vm/new/{{m.pk}}/">{% csrf_token %}
-                        <div class="summary">
-                            <div class="name wm-on">{{m.name}}</div>
-                            <div class="status">
-                                <input type="submit" value="Indítás"/>
-                            </div>
-                            <div class="clear"></div>
-                        </div>
-                        <div class="details">
-                            <h3>
-                                {% trans "Details" %}
-                            </h3>
-                            <ul>
-                                <li class="name">{% trans "System" %}: <span class="value">{{m.disk.name}}</span></li>
-                                <li class="type">{% trans "Size" %}: <span class="value">{{m.instance_type.name}}</span></li>
-                                <li class="memory">{% trans "Memory" %}: <span class="value">{{m.instance_type.RAM}} MiB</span></li>
-                                <li class="cpu">{% trans "CPU cores" %}: <span class="value">{{m.instance_type.CPU}}</span></li>
-                            </ul>
-                        </div>
-                    </form>
-                </li>
-                {% endfor %}
-            </ul>
-        </div>
-        <nav>
-            <a href="#" class="prev">{% trans "&laquo; Cancel" %}</a>
-            <a href="#" class="next">{% trans "Next &raquo;" %}</a>
-            <div class="clear"></div>
-        </nav>
-        <script type="text/javascript">
-            $(function(){
-
-                    console.log('foo');
-                $('#modal .wm .summary').each(function(){
-
-                    $(this).next('.details').show();
-                    console.log($(this).next('.details').css('display'), $(this).next('.details').css('height'));
-                    //this.originalHeight=parseInt($(this).next('.details').css('height'));
-                })
-                $('#modal .wm .summary').click(function(){
-                    if($(this).next('.details').is(':hidden')){
-                        $(this).next('.details')
-                            .slideDown(700);
-                        $(this).parent('.wm').addClass('opened');
-                    } else {
-                        var that=this;
-                        $(this).next('.details')
-                            .removeClass('opened')
-                            .slideUp(700);
-                }
-            });
-                $('#new-template-step-1 nav .prev').click(function(){
-                    $('#new-template-step-1').hide();
-                    $('#new-template-step-1').show();
-                })
-                $('#new-template-step-1 nav .next').click(function(){
-                    $('#new-template-step-1').hide();
-                    $('#new-template-step-3').show();
-                    $.ajax({
-                        'type': 'POST',
-                        'url': '/ajax/templateWizard',
-                        'data': $('#template-wizard').serialize()
-                    })
-                    .done(function(){ console.log('ok')});
-                })
-            })
-        </script>
-    </div>
-    <div id="new-template-step-2" class="wizard" style="display: none">
+<form action="/vm/new/{{base.id}}/" method="post" id="template-wizard">{% csrf_token %}
+    <div id="new-template-step-2" class="wizard">
         <div class="progress">
             <div class="bar-container">
                 <div class="bar" style="width: 66%"></div>
@@ -90,69 +9,77 @@
             <h3>{% blocktrans with step=2 all=3 %}{{step}}/{{all}}{% endblocktrans %}</h3>
         </div>
         <h2>{% blocktrans with step=2 %}Step {{step}}{% endblocktrans %}</h2>
-        <p>Leírás, mit is kéne itt ezen az ablakon csinálni, és miért jó, ha azt csinálja, amit.</p>
+        <p>{% trans "Change the parameters as needed." %}</p>
         <ul>
             <li>
             <label for="new-template-name">{% trans "Name" %}</label>
-                <input type="text" placeholder="{% trans "Short name of template" %}" name="name" id="new-template-name" />
+                <input type="text" name="name" id="new-template-name" value="{{base.name}}"
+                class="error validated" title="{% trans "Please choose a different name." %}" />
                 <div class="clear"></div>
             </li>
-            <li>
-            <label for="new-template-type">{% trans "Type" %}</label>
-                <ul class="radio">
-                    <li>
-                        <input type="radio" name="type" value="labor" id="new-template-type-labor" />
-                        <label for="new-template-type-labor">{% trans "Lab" %}</label>
-                    </li>
-                    <li>
-                        <input type="radio" name="type" value="project" id="new-template-type-project" />
-                        <label for="new-template-type-project">{% trans "Project" %}</label>
-                    </li>
-                </ul>
-                <div class="clear"></div>
-            </li>
-            <li>
+            <li class="new-tpl-size">
                 <label for="new-template-size">{% trans "Size" %}</label>
                 <ul class="radio">
+                    {% for s in sizes %}
                     <li>
-                        <input type="radio" name="size" value="small" id="new-template-size-small" />
-                        <label for="new-template-size-small">Kicsi</label>
-                    </li>
-                    <li>
-                        <input type="radio" name="size" value="medium" id="new-template-size-medium" />
-                        <label for="new-template-size-medium">Közepes</label>
-                    </li>
-                    <li>
-                        <input type="radio" name="size" value="large" id="new-template-size-large" />
-                        <label for="new-template-size-large">Nagy</label>
+                        <label>
+                            <input type="radio" name="size" value="{{s.id}}" id="new-template-size-{{s.id}}"
+                            {% if s == base.instance_type %}checked="checked"{% endif %} />
+                            {{s.name}}
+                        </label>
                     </li>
+                    {% endfor %}
                 </ul>
+                {% for s in sizes %}
+                <p id="new-template-size-summary-{{s.id}}" class="size-summary clear"
+                {% if s != base.instance_type %}style="display:none"{% endif %}>
+                    <span class="cpu">{% blocktrans count n=s.CPU %}{{n}} core{% plural %}{{n}} cores{% endblocktrans %}</span>
+                    <span class="memory">{{s.RAM}} MiB</span>
+                    <span class="credit">{{s.credit}}</span>
+                </p>
+                {% endfor %}
                 <div class="clear"></div>
             </li>
             <li style="border: none">
                 <label for="new-template-description">{% trans "Description" %}</label>
-                <textarea name="description" id="new-template-description" placeholder="{% trans "Comments about the template" %}"></textarea>
+                <textarea name="description" id="new-template-description" style="text-align: left">{{base.description}}</textarea>
                 <div class="clear"></div>
             </li>
         </ul>
         <nav>
             <a href="#" class="prev">{% trans "&laquo; Cancel" %}</a>
-            <a href="#" class="next">{% trans "Next &raquo;" %}</a>
+            <input type="submit" value="{% trans "Launch" %}" />
             <div class="clear"></div>
         </nav>
         <script type="text/javascript">
             $(function(){
-                $('#new-template-step-1 nav .next').click(function(){
-                    $('#new-template-step-1').hide();
-                    $('#new-template-step-2').show();
-                })
-                $('#new-template-step-1 nav .prev').click(function(){
+                $('#new-template-step-2 nav .prev').click(function(){
                     $('#modal').hide();
                 })
+                $(".new-tpl-size input[name='size']").click(function(e){
+                    var v = $(".new-tpl-size input[name='size']:checked").val();
+                    $("p.size-summary").hide();
+                    $("#new-template-size-summary-" + v).show();
+                });
+                $("#new-template-name").change(function(e){
+                    var s = $(this).val();
+                    $.ajax({
+                        'type': 'GET',
+                        'url': '/ajax/template_name_unique/' + s,
+                        'success': function(data, b, c) {
+                            if (s != $("#new-template-name").val()) {
+                                return True;
+                            }
+                            if (data == "True") {
+                                $('#new-template-name').removeClass("error");
+                            }
+                            else {
+                                $('#new-template-name').addClass("error");
+                            }
+                        }
+                    });
+                });
             })
         </script>
     </div>
-    <div id="new-template-step-3" class="wizard" style="display: none">
-
-    </div>
 </form>
diff --git a/one/templates/show.html b/one/templates/show.html
index 0209cf3..8e50bbb 100644
--- a/one/templates/show.html
+++ b/one/templates/show.html
@@ -21,6 +21,29 @@
 {% endblock %}
 
 {% block content %}
+
+{% if i.template.state != "READY" %}
+<div class="contentblock wide note big">
+    <p>{% blocktrans %}This is a master image for your new template.{% endblocktrans %}</p>
+    <form action="{% url one.views.vm_saveas id %}" method="POST">{% csrf_token %}
+        {% if i.template.state == "NEW" %}
+            <p style="float: right; margin-top:2em;margin-right:1em;">
+                <input type="submit" value="{% trans "Save" %}" class="big" style="background-color:rgba(102, 255, 0, 0.4)" />
+            </p>
+        {% endif %}
+    </form>
+    <ol>
+        <li{% if i.template.state == "SAVING" %} class="done"{%endif%}>{% blocktrans %}Connect to the machine.{% endblocktrans %}</li>
+        <li{% if i.template.state == "SAVING" %} class="done"{%endif%}>{% blocktrans %}Do all the needed installation/customization.{% endblocktrans %}</li>
+        <li{% if i.template.state == "SAVING" %} class="done"{%endif%}>{% blocktrans %}Log off (keep the machine running).{% endblocktrans %}</li>
+        <li{% if i.template.state == "SAVING" %} class="done"{%endif%}>{% blocktrans %}Click on the "save" button on the right.{% endblocktrans %}</li>
+        <li>{% blocktrans %}The machine will be shut down and its disk saved.{% endblocktrans %}</li>
+        <li>{% blocktrans %}You can share the template with your groups.{% endblocktrans %}</li>
+    </ol>
+</div>
+{% endif %}
+
+
 <div class="boxes">
     <div class="contentblock" id="state">
         <h2>{{name}}</h2>
diff --git a/one/views.py b/one/views.py
index 09344ae..151ff29 100644
--- a/one/views.py
+++ b/one/views.py
@@ -10,7 +10,6 @@ from django.core.mail import mail_managers, send_mail
 from django.db import transaction
 from django.forms import ModelForm, Textarea
 from django.http import Http404
-#from django_shibboleth.forms import BaseRegisterForm
 from django.shortcuts import render, render_to_response, get_object_or_404, redirect
 from django.template import RequestContext
 from django.template.loader import render_to_string
@@ -19,11 +18,14 @@ from django.utils.translation import get_language as lang
 from django.utils.translation import ugettext_lazy as _
 from django.views.decorators.http import *
 from django.views.generic import *
+from firewall.tasks import *
 from one.models import *
 from school.models import *
 import django.contrib.auth as auth
-from firewall.tasks import *
 import json
+import logging
+
+logger = logging.getLogger(__name__)
 
 class LoginView(View):
     def get(self, request, *args, **kwargs):
@@ -32,12 +34,12 @@ class LoginView(View):
             nex = request.GET['next']
         except:
             pass
-        return render_to_response("login.html", RequestContext(request,{'next': nex}))
+        return render_to_response("login.html", RequestContext(request, {'next': nex}))
     def post(self, request, *args, **kwargs):
         if request.POST['pw'] != 'ezmiez':
             return redirect('/')
         p, created = User.objects.get_or_create(username=request.POST['neptun'])
-	if created:
+        if created:
             p.set_unusable_password()
         if not p.email:
             p.email = "%s@nc.hszk.bme.hu" % p.username
@@ -70,29 +72,66 @@ def _list_instances(request):
 @require_GET
 @login_required
 def home(request):
-    return render_to_response("home.html", RequestContext(request,{
-        'templates': Template.objects.all(),
+    return render_to_response("home.html", RequestContext(request, {
+        'templates': Template.objects.filter(state='READY'),
+        'mytemplates': Template.objects.filter(owner=request.user),
         'instances': _list_instances(request),
         'groups': request.user.person_set.all()[0].owned_groups.all(),
         'semesters': Semester.objects.all()
         }))
 
-@require_GET
+def ajax_template_name_unique(request, name):
+    s = "True"
+    if Template.objects.filter(name=name).exists():
+        s = "False"
+    return HttpResponse(s)
+
+class AjaxTemplateWizard(View):
+    def get(self, request, *args, **kwargs):
+        return render_to_response('new-template-flow-1.html', RequestContext(request,{
+            'templates': Template.objects.filter(public=True)
+                # + Template.objects.filter(owner=request.user),
+            }))
+    def post(self, request, *args, **kwargs):
+        base = get_object_or_404(Template, id=request.POST['base'])
+        if base.owner != request.user and not base.public and not request.user.is_superuser:
+            raise PermissionDenied()
+        return render_to_response('new-template-flow.html', RequestContext(request, {
+            'sizes': InstanceType.objects.all(),
+            'base': base,
+            }))
+ajax_template_wizard = login_required(AjaxTemplateWizard.as_view())
+
+@require_POST
 @login_required
-def ajax_template_wizard(request):
-    return render_to_response('new-template-flow.html', RequestContext(request,{
-        'templates': Template.objects.all(),
-        }))
+def vm_saveas(request, vmid):
+    inst = get_object_or_404(Instance, pk=vmid)
+    if inst.owner != request.user and not request.user.is_superuser:
+        raise PermissionDenied()
+    inst.save_as()
+    messages.success(request, _("Template is being saved..."))
+    return redirect(inst)
+
 
 @require_POST
 @login_required
 def vm_new(request, template):
-    m = get_object_or_404(Template, pk=template)
+    base = get_object_or_404(Template, pk=template)
+    if "name" in request.POST:
+        if base.owner != request.user and not base.public and not request.user.is_superuser:
+            raise PermissionDenied()
+        name = request.POST['name']
+        t = Template.objects.create(name=name, disk=base.disk, instance_type_id=request.POST['size'], network=base.network, owner=request.user)
+        t.access_type = base.access_type
+        t.description = request.POST['description']
+        t.system = base.system
+        t.save()
+        base = t
     try:
-        i = Instance.submit(m, request.user)
+        i = Instance.submit(base, request.user, extra="<RECONTEXT>YES</RECONTEXT>")
         return redirect(i)
-    except:
-        raise
+    except Exception as e:
+        logger.error('Failed to create virtual machine.' + unicode(e))
         messages.error(request, _('Failed to create virtual machine.'))
         return redirect('/')
 
@@ -111,6 +150,8 @@ vm_list = login_required(VmListView.as_view())
 def vm_show(request, iid):
     inst = get_object_or_404(Instance, id=iid, owner=request.user)
     inst.update_state()
+    if inst.template.state == "SAVING":
+        inst.check_if_is_save_as_done()
     return render_to_response("show.html", RequestContext(request,{
         'uri': inst.get_connect_uri(),
         'state': inst.state,
@@ -188,7 +229,7 @@ class VmDeleteView(View):
 
     def get(self, request, iid, *args, **kwargs):
         i = get_object_or_404(Instance, id=iid, owner=request.user)
-        return render_to_response("confirm_delete.html", RequestContext(request,{
+        return render_to_response("confirm_delete.html", RequestContext(request, {
             'i': i}))
 
 vm_delete = login_required(VmDeleteView.as_view())
--
libgit2 0.26.0