Merge pull request #217 from levivm/opennebula-integration

Opennebula integration
This commit is contained in:
Levi Velázquez 2017-05-06 18:27:35 -05:00 committed by GitHub
commit e1a61ba930
23 changed files with 756 additions and 226 deletions

View file

@ -1,10 +1,13 @@
import random
import string
from django import forms from django import forms
from membership.models import CustomUser from membership.models import CustomUser
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from .models import HostingOrder, VirtualMachinePlan from .models import HostingOrder, VirtualMachinePlan, UserHostingKey
class HostingOrderAdminForm(forms.ModelForm): class HostingOrderAdminForm(forms.ModelForm):
@ -83,3 +86,40 @@ class HostingUserSignupForm(forms.ModelForm):
if not confirm_password == password: if not confirm_password == password:
raise forms.ValidationError("Passwords don't match") raise forms.ValidationError("Passwords don't match")
return confirm_password return confirm_password
class UserHostingKeyForm(forms.ModelForm):
private_key = forms.CharField(widget=forms.PasswordInput(), required=False)
public_key = forms.CharField(widget=forms.PasswordInput(), required=False)
user = forms.models.ModelChoiceField(queryset=CustomUser.objects.all(), required=False)
name = forms.CharField(required=False)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
super(UserHostingKeyForm, self).__init__(*args, **kwargs)
# self.initial['user'].initial = self.request.user.id
# print(self.fields)
def clean_name(self):
return ''.join(random.choice(string.ascii_lowercase) for i in range(7))
def clean_user(self):
return self.request.user
def clean(self):
cleaned_data = self.cleaned_data
print(cleaned_data)
if not cleaned_data.get('public_key'):
private_key, public_key = UserHostingKey.generate_keys()
cleaned_data.update({
'private_key': private_key,
'public_key': public_key
})
return cleaned_data
class Meta:
model = UserHostingKey
fields = ['user', 'public_key', 'name']

View file

@ -7,51 +7,100 @@ class Command(BaseCommand):
def get_data(self): def get_data(self):
return [
{
'base_price': 10,
'core_price': 5,
'memory_price': 2,
'disk_size_price': 0.6,
'cores': 1,
'memory': 2,
'disk_size': 10
},
{
'base_price': 10,
'core_price': 5,
'memory_price': 2,
'disk_size_price': 0.6,
'cores': 1,
'memory': 2,
'disk_size': 100
},
{
'base_price': 10,
'core_price': 5,
'memory_price': 2,
'disk_size_price': 0.6,
'cores': 2,
'memory': 4,
'disk_size': 20
},
{
'base_price': 10,
'core_price': 5,
'memory_price': 2,
'disk_size_price': 0.6,
'cores': 4,
'memory': 8,
'disk_size': 40
},
{
'base_price': 10,
'core_price': 5,
'memory_price': 2,
'disk_size_price': 0.6,
'cores': 16,
'memory': 8,
'disk_size': 40
},
]
hetzner = { hetzner = {
'base_price': 10, 'base_price': 10,
'core_price': 10, 'core_price': 5,
'memory_price': 5, 'memory_price': 2,
'disk_size_price': 1, 'disk_size_price': 0.6,
'description': 'VM auf einzelner HW, Raid1, kein HA', 'description': 'VM auf einzelner HW, Raid1, kein HA',
'location': 'DE' 'location': 'DE'
} }
return { # return {
# 'hetzner_nug': { # # 'hetzner_nug': {
# 'base_price': 5, # # 'base_price': 5,
# 'memory_price': 2, # # 'memory_price': 2,
# 'core_price': 2, # # 'core_price': 2,
# 'disk_size_price': 0.5, # # 'disk_size_price': 0.5,
# 'description': 'VM ohne Uptime Garantie' # # 'description': 'VM ohne Uptime Garantie'
# }, # # },
'hetzner': hetzner, # 'hetzner': hetzner,
# 'hetzner_raid6': { # # 'hetzner_raid6': {
# 'base_price': hetzner['base_price']*1.2, # # 'base_price': hetzner['base_price']*1.2,
# 'core_price': hetzner['core_price']*1.2, # # 'core_price': hetzner['core_price']*1.2,
# 'memory_price': hetzner['memory_price']*1.2, # # 'memory_price': hetzner['memory_price']*1.2,
# 'disk_size_price': hetzner['disk_size_price']*1.2, # # 'disk_size_price': hetzner['disk_size_price']*1.2,
# 'description': 'VM auf einzelner HW, Raid1, kein HA' # # 'description': 'VM auf einzelner HW, Raid1, kein HA'
# }, # # },
# 'hetzner_glusterfs': { # # 'hetzner_glusterfs': {
# 'base_price': hetzner['base_price']*1.4, # # 'base_price': hetzner['base_price']*1.4,
# 'core_price': hetzner['core_price']*1.4, # # 'core_price': hetzner['core_price']*1.4,
# 'memory_price': hetzner['memory_price']*1.4, # # 'memory_price': hetzner['memory_price']*1.4,
# 'disk_size_price': hetzner['disk_size_price']*1.4, # # 'disk_size_price': hetzner['disk_size_price']*1.4,
# 'description': 'VM auf einzelner HW, Raid1, kein HA' # # 'description': 'VM auf einzelner HW, Raid1, kein HA'
# }, # # },
'bern': { # 'bern': {
'base_price': 12, # 'base_price': 12,
'core_price': 25, # 'core_price': 25,
'memory_price': 7, # 'memory_price': 7,
'disk_size_price': 0.70, # 'disk_size_price': 0.70,
'description': "VM in Bern, HA Setup ohne HA Garantie", # 'description': "VM in Bern, HA Setup ohne HA Garantie",
'location': 'CH', # 'location': 'CH',
} # }
} # }
def handle(self, *args, **options): def handle(self, *args, **options):
data = self.get_data() vm_data = self.get_data()
[VirtualMachineType.objects.create(hosting_company=key, **data[key]) for vm in vm_data:
for key in data.keys()] VirtualMachineType.objects.create(**vm)

View file

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-04-29 18:28
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('hosting', '0027_auto_20160711_0210'),
]
operations = [
migrations.CreateModel(
name='ManageVM',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
],
options={
'managed': False,
},
),
migrations.CreateModel(
name='UserHostingKey',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('public_key', models.TextField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-04-30 19:04
from __future__ import unicode_literals
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
class Migration(migrations.Migration):
dependencies = [
('hosting', '0028_managevm_userhostingkey'),
]
operations = [
migrations.AddField(
model_name='userhostingkey',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2017, 4, 30, 19, 4, 20, 780173, tzinfo=utc)),
preserve_default=False,
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-04-30 19:09
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0029_userhostingkey_created_at'),
]
operations = [
migrations.AddField(
model_name='userhostingkey',
name='name',
field=models.CharField(default='', max_length=100),
preserve_default=False,
),
]

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-03 05:54
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('hosting', '0030_userhostingkey_name'),
]
operations = [
migrations.RemoveField(
model_name='virtualmachinetype',
name='hosting_company',
),
migrations.RemoveField(
model_name='virtualmachinetype',
name='location',
),
]

View file

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-04 03:15
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0031_auto_20170503_0554'),
]
operations = [
migrations.AddField(
model_name='virtualmachinetype',
name='cores',
field=models.IntegerField(default=0),
preserve_default=False,
),
migrations.AddField(
model_name='virtualmachinetype',
name='disk_size',
field=models.IntegerField(default=0),
preserve_default=False,
),
migrations.AddField(
model_name='virtualmachinetype',
name='memory',
field=models.IntegerField(default=0),
preserve_default=False,
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-04 03:23
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0032_auto_20170504_0315'),
]
operations = [
migrations.AddField(
model_name='virtualmachinetype',
name='configuration',
field=models.CharField(choices=[('debian', 'Debian 8'), ('ubuntu', 'Ubuntu 16.06'), ('devuan', 'Devuan 1'), ('centos', 'CentOS 7')], default='ubuntu', max_length=10),
),
]

View file

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-04 03:31
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('hosting', '0033_virtualmachinetype_configuration'),
]
operations = [
migrations.RemoveField(
model_name='virtualmachinetype',
name='configuration',
),
migrations.AlterField(
model_name='virtualmachineplan',
name='configuration',
field=models.CharField(choices=[('debian', 'Debian 8'), ('ubuntu', 'Ubuntu 16.06'), ('devuan', 'Devuan 1'), ('centos', 'CentOS 7')], max_length=20),
),
migrations.AlterField(
model_name='virtualmachineplan',
name='vm_type',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='hosting.VirtualMachineType'),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-06 23:02
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0034_auto_20170504_0331'),
]
operations = [
migrations.AddField(
model_name='virtualmachineplan',
name='opennebula_id',
field=models.IntegerField(default=0),
preserve_default=False,
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-05-06 23:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hosting', '0035_virtualmachineplan_opennebula_id'),
]
operations = [
migrations.AlterField(
model_name='virtualmachineplan',
name='opennebula_id',
field=models.IntegerField(null=True),
),
]

View file

@ -1,23 +1,32 @@
from django.shortcuts import redirect from django.shortcuts import redirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from .models import VirtualMachinePlan from .models import VirtualMachinePlan, VirtualMachineType
class ProcessVMSelectionMixin(object): class ProcessVMSelectionMixin(object):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
hosting = request.POST.get('configuration') configuration = request.POST.get('configuration')
configuration_detail = dict(VirtualMachinePlan.VM_CONFIGURATION).get(hosting) configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration)
vm_specs = { vm_template = request.POST.get('vm_template')
'cores': request.POST.get('cores'), vm_type = VirtualMachineType.objects.get(id=vm_template)
'memory': request.POST.get('memory'), vm_specs = vm_type.get_specs()
'disk_size': request.POST.get('disk_space'), vm_specs.update({
'hosting_company': request.POST.get('hosting_company'), 'configuration_display': configuration_display,
'location_code': request.POST.get('location_code'), 'configuration': configuration,
'configuration': hosting, 'final_price': vm_type.final_price,
'configuration_detail': configuration_detail, 'vm_template': vm_template
'final_price': request.POST.get('final_price') })
} # vm_specs = {
# # 'cores': request.POST.get('cores'),
# # 'memory': request.POST.get('memory'),
# # 'disk_size': request.POST.get('disk_space'),
# # 'hosting_company': request.POST.get('hosting_company'),
# # 'location_code': request.POST.get('location_code'),
# # 'configuration': hosting,
# # 'configuration_detail': configuration_detail,
# 'final_price': request.POST.get('final_price')
# }
request.session['vm_specs'] = vm_specs request.session['vm_specs'] = vm_specs
if not request.user.is_authenticated(): if not request.user.is_authenticated():
request.session['vm_specs'] = vm_specs request.session['vm_specs'] = vm_specs

View file

@ -7,7 +7,7 @@ from django.utils.functional import cached_property
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from stored_messages.settings import stored_messages_settings from stored_messages.settings import stored_messages_settings
from membership.models import StripeCustomer from membership.models import StripeCustomer, CustomUser
from utils.models import BillingAddress from utils.models import BillingAddress
from utils.mixins import AssignPermissionsMixin from utils.mixins import AssignPermissionsMixin
from .managers import VMPlansManager from .managers import VMPlansManager
@ -15,70 +15,71 @@ from .managers import VMPlansManager
class VirtualMachineType(models.Model): class VirtualMachineType(models.Model):
HETZNER_NUG = 'hetzner_nug'
HETZNER = 'hetzner'
HETZNER_R6 = 'hetzner_raid6'
HETZNER_G = 'hetzner_glusterfs'
BERN = 'bern'
DE_LOCATION = 'DE'
CH_LOCATION = 'CH'
HOSTING_TYPES = (
(HETZNER_NUG, 'Hetzner No Uptime Guarantee'),
(HETZNER, 'Hetzner'),
(HETZNER_R6, 'Hetzner Raid6'),
(HETZNER_G, 'Hetzner Glusterfs'),
(BERN, 'Bern'),
)
LOCATIONS_CHOICES = (
(DE_LOCATION, 'Germany'),
(CH_LOCATION, 'Switzerland'),
)
description = models.TextField() description = models.TextField()
base_price = models.FloatField() base_price = models.FloatField()
memory_price = models.FloatField() memory_price = models.FloatField()
core_price = models.FloatField() core_price = models.FloatField()
disk_size_price = models.FloatField() disk_size_price = models.FloatField()
hosting_company = models.CharField(max_length=30, choices=HOSTING_TYPES) cores = models.IntegerField()
location = models.CharField(max_length=3, choices=LOCATIONS_CHOICES) memory = models.IntegerField()
disk_size = models.IntegerField()
def __str__(self): def __str__(self):
return "%s" % (self.get_hosting_company_display()) return "VM Type %s" % (self.id)
@cached_property
def final_price(self):
price = self.cores * self.core_price
price += self.memory * self.memory_price
price += self.disk_size * self.disk_size_price
return price
@classmethod @classmethod
def get_serialized_vm_types(cls): def get_serialized_vm_types(cls):
return [vm.get_serialized_data() return [vm.get_serialized_data()
for vm in cls.objects.all()] for vm in cls.objects.all()]
def calculate_price(self, specifications): def calculate_price(self):
price = float(specifications['cores']) * self.core_price price = self.cores * self.core_price
price += float(specifications['memory']) * self.memory_price price += self.memory * self.memory_price
price += float(specifications['disk_size']) * self.disk_size_price price += self.disk_size * self.disk_size_price
price += self.base_price # price += self.base_price
return price return price
def defeault_price(self): # @classmethod
price = self.base_price # def get_price(cls, vm_template):
price += self.core_price # return cls.BASE_PRICE * vm_template
price += self.memory_price
price += self.disk_size_price * 10 def get_specs(self):
return price return {
'memory': self.memory,
'cores': self.cores,
'disk_size': self.disk_size
}
# def calculate_price(self, vm_template):
# price = self.base_price * vm_template
# return price
# def defeault_price(self):
# price = self.base_price
# price += self.core_price
# price += self.memory_price
# price += self.disk_size_price * 10
# return price
def get_serialized_data(self): def get_serialized_data(self):
return { return {
'description': self.description, 'description': self.description,
'base_price': self.base_price,
'core_price': self.core_price, 'core_price': self.core_price,
'disk_size_price': self.disk_size_price, 'disk_size_price': self.disk_size_price,
'memory_price': self.memory_price, 'memory_price': self.memory_price,
'hosting_company_name': self.get_hosting_company_display(),
'hosting_company': self.hosting_company,
'default_price': self.defeault_price(),
'location_code': self.location,
'location': self.get_location_display(),
'id': self.id, 'id': self.id,
'final_price': self.final_price,
'cores': self.cores,
'memory': self.memory,
'disk_size': self.disk_size
} }
@ -94,14 +95,21 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model):
(CANCELED_STATUS, 'Canceled') (CANCELED_STATUS, 'Canceled')
) )
DJANGO = 'django' # DJANGO = 'django'
RAILS = 'rails' # RAILS = 'rails'
NODEJS = 'nodejs' # NODEJS = 'nodejs'
# VM_CONFIGURATION = (
# (DJANGO, 'Ubuntu 14.04, Django'),
# (RAILS, 'Ubuntu 14.04, Rails'),
# (NODEJS, 'Debian, NodeJS'),
# )
VM_CONFIGURATION = ( VM_CONFIGURATION = (
(DJANGO, 'Ubuntu 14.04, Django'), ('debian', 'Debian 8'),
(RAILS, 'Ubuntu 14.04, Rails'), ('ubuntu', 'Ubuntu 16.06'),
(NODEJS, 'Debian, NodeJS'), ('devuan', 'Devuan 1'),
('centos', 'CentOS 7')
) )
permissions = ('view_virtualmachineplan', permissions = ('view_virtualmachineplan',
@ -111,12 +119,13 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model):
cores = models.IntegerField() cores = models.IntegerField()
memory = models.IntegerField() memory = models.IntegerField()
disk_size = models.IntegerField() disk_size = models.IntegerField()
vm_type = models.ForeignKey(VirtualMachineType) vm_type = models.ForeignKey(VirtualMachineType, null=True)
price = models.FloatField() price = models.FloatField()
public_key = models.TextField(blank=True) public_key = models.TextField(blank=True)
status = models.CharField(max_length=20, choices=VM_STATUS_CHOICES, default=PENDING_STATUS) status = models.CharField(max_length=20, choices=VM_STATUS_CHOICES, default=PENDING_STATUS)
ip = models.CharField(max_length=50, blank=True) ip = models.CharField(max_length=50, blank=True)
configuration = models.CharField(max_length=20, choices=VM_CONFIGURATION) configuration = models.CharField(max_length=20, choices=VM_CONFIGURATION)
opennebula_id = models.IntegerField(null=True)
objects = VMPlansManager() objects = VMPlansManager()
@ -129,13 +138,13 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
@cached_property # @cached_property
def hosting_company_name(self): # def hosting_company_name(self):
return self.vm_type.get_hosting_company_display() # return self.vm_type.get_hosting_company_display()
@cached_property # @cached_property
def location(self): # def location(self):
return self.vm_type.get_location_display() # return self.vm_type.get_location_display()
@cached_property @cached_property
def name(self): def name(self):
@ -155,24 +164,6 @@ class VirtualMachinePlan(AssignPermissionsMixin, models.Model):
instance.assign_permissions(user) instance.assign_permissions(user)
return instance return instance
@staticmethod
def generate_RSA(bits=2048):
'''
Generate an RSA keypair with an exponent of 65537 in PEM format
param: bits The key length in bits
Return private key and public key
'''
new_key = RSA.generate(2048, os.urandom)
public_key = new_key.publickey().exportKey("OpenSSH")
private_key = new_key.exportKey("PEM")
return private_key, public_key
def generate_keys(self):
private_key, public_key = self.generate_RSA()
self.public_key = public_key
self.save(update_fields=['public_key'])
return private_key, public_key
def cancel_plan(self): def cancel_plan(self):
self.status = self.CANCELED_STATUS self.status = self.CANCELED_STATUS
self.save(update_fields=['status']) self.save(update_fields=['status'])
@ -224,6 +215,32 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
self.save() self.save()
class UserHostingKey(models.Model):
user = models.ForeignKey(CustomUser)
public_key = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100)
@staticmethod
def generate_RSA(bits=2048):
'''
Generate an RSA keypair with an exponent of 65537 in PEM format
param: bits The key length in bits
Return private key and public key
'''
new_key = RSA.generate(2048, os.urandom)
public_key = new_key.publickey().exportKey("OpenSSH")
private_key = new_key.exportKey("PEM")
return private_key, public_key
@classmethod
def generate_keys(cls):
private_key, public_key = cls.generate_RSA()
# self.public_key = public_key
# self.save(update_fields=['public_key'])
return private_key, public_key
class ManageVM(models.Model): class ManageVM(models.Model):
def has_add_permission(self, request): def has_add_permission(self, request):
return False return False

View file

@ -87,6 +87,48 @@ class HostingManageVMAdmin(admin.ModelAdmin):
) )
return TemplateResponse(request, "hosting/managevms.html", context) return TemplateResponse(request, "hosting/managevms.html", context)
def create_vm_view(self, specs):
vm_id = None
try:
# We do have the vm_template param set. Get and parse it
# and check it to be in the desired range.
# We have 8 possible VM templates for the moment which are 1x, 2x, 4x ...
# the basic template of 10GB disk, 1GB ram, 1 vcpu, 0.1 cpu
vm_string_formatter = """<VM>
<MEMORY>{memory}</MEMORY>
<VCPU>{vcpu}</VCPU>
<CPU>{cpu}</CPU>
<DISK>
<TYPE>{disk_type}</TYPE>
<SIZE>{size}</SIZE>
</DISK>
</VM>
"""
vm_id = oca.VirtualMachine.allocate(
self.client,
vm_string_formatter.format(
memory=1024 * specs.get('memory'),
vcpu=specs.get('cores'),
cpu=0.1 * specs.get('cores'),
disk_type='fs',
size=10000 * specs.get('disk_size')
)
)
# message = _("Created with id = " + str(vm_id))
# messages.add_message(request, messages.SUCCESS, message)
except socket.timeout as socket_err:
logger.error("Socket timeout error: {0}".format(socket_err))
except OpenNebulaException as opennebula_err:
logger.error("OpenNebulaException error: {0}".format(opennebula_err))
except OSError as os_err:
logger.error("OSError : {0}".format(os_err))
except ValueError as value_err:
logger.error("ValueError : {0}".format(value_err))
return vm_id
# Creating VM by using method allocate(client, template) # Creating VM by using method allocate(client, template)
def create_vm(self, request): def create_vm(self, request):
# check if the request contains the template parameter, if it is # check if the request contains the template parameter, if it is

View file

@ -72,6 +72,11 @@
<i class="fa fa-credit-card"></i> {% trans "My Orders"%} <i class="fa fa-credit-card"></i> {% trans "My Orders"%}
</a> </a>
</li> </li>
<li>
<a href="{% url 'hosting:key_pair' %}">
<i class="fa fa-key" aria-hidden="true"></i> {% trans "Keys"%}
</a>
</li>
<li> <li>
<a href="{% url 'hosting:notifications' %}"> <a href="{% url 'hosting:notifications' %}">
<i class="fa fa-bell"></i> {% trans "Notifications "%} <i class="fa fa-bell"></i> {% trans "Notifications "%}

View file

@ -0,0 +1,45 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 i18n %}
{% block content %}
<div>
<div class="container dashboard-container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h3><i class="fa fa-server" aria-hidden="true"></i> {% trans "New Virtual Machine"%} </h3>
<hr/>
<form method="POST" action="">
{% csrf_token %}
<div class="form-group">
Select VM:
<select name="vm_template">
{% for vm in vm_types %}
<option value="{{vm.id}}">CORE: {{vm.cores}}, RAM: {{vm.memory}}, SSD: {{vm.disk_size}} </option>
{% endfor %}
</select>
</div>
<div class="form-group">
Select VM Configuration:
<select name="configuration">
{% for config in configuration_options %}
<option value="{{config.0}}">{{config.1}} </option>
{% endfor %}
</select>
</div>
<div class="form-group">
<button class="btn btn-success" >{% trans "Start VM"%} </button>
</div>
</form>
</div>
</div>
</div>
</div>
{%endblock%}

View file

@ -24,58 +24,17 @@
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="hosting_company" value="{{vm.hosting_company}}"> <input type="hidden" name="hosting_company" value="{{vm.hosting_company}}">
<input type="hidden" name="location_code" value="{{vm.location_code}}"> <input type="hidden" name="location_code" value="{{vm.location_code}}">
<input type="hidden" name="vm_template" value="{{vm.id}}">
<ul class="pricing {% cycle 'p-red' 'p-black' 'p-red' 'p-yel' %}"> <ul class="pricing {% cycle 'p-red' 'p-black' 'p-red' 'p-yel' %}">
<li class="type">
<!-- <img src="http://bread.pp.ua/n/settings_g.svg" alt=""> -->
<h3 >{{vm.location_code}}</h3>
<br/>
<img class="img-responsive" src="{{ STATIC_URL }}hosting/img/{{vm.location_code}}_flag.png" alt="">
</li>
<li> <li>
<!-- Single button --> <!-- Single button -->
<div class="btn-group"> <div class="btn-group">
<div class="form-group"> <div class="form-group">
<label for="cores">Location: </label> <label for="cores">Cores: {{vm.cores}}</label>
{{vm.location}}
</div>
</div>
</li>
<li>
<label for="configuration">Configuration: </label>
{% if select_configuration %}
<select class="form-control" name="configuration" id="{{vm.hosting_company}}-configuration" data-vm-type="{{vm.hosting_company}}">
{% for key,value in configuration_options.items %}
<option value="{{key}}">{{ value }}</option>
{% endfor %}
</select>
{% else %}
<input type="hidden" name="configuration_detail" value="{{configuration_detail}}">
<input type="hidden" name="configuration" value="{{hosting}}">
<!-- Single button -->
<div class="btn-group">
<div class="form-group">
<label>Configuration: </label>
{{configuration_detail}}
</div>
</div>
{% endif %}
</li>
<li>
<!-- Single button -->
<div class="btn-group">
<div class="form-group">
<label for="cores">Cores: </label>
<select class="form-control cores-selector" name="cores" id="{{vm.hosting_company}}-cores" data-vm-type="{{vm.hosting_company}}">
{% with ''|center:10 as range %}
{% for _ in range %}
<option>{{ forloop.counter }}</option>
{% endfor %}
{% endwith %}
</select>
</div> </div>
</div> </div>
@ -83,30 +42,28 @@
<li> <li>
<div class="form-group"> <div class="form-group">
<div class="btn-group"> <div class="btn-group">
<label for="memory">Memory: </label> <label for="memory">Memory: {{vm.memory}} GiB</label>
<select class="form-control memory-selector" name="memory" id="{{vm.hosting_company}}-memory" data-vm-type="{{vm.hosting_company}}">
{% with ''|center:50 as range %}
{% for _ in range %}
<option>{{ forloop.counter }}</option>
{% endfor %}
{% endwith %}
</select>
<span>GiB</span>
</div> </div>
</div> </div>
</li> </li>
<li> <li>
<div class="form-group row"> <div class="form-group row">
<div class="col-xs-offset-1 col-xs-9 col-sm-12 col-md-12 col-md-offset-0"> <div class="col-xs-offset-1 col-xs-9 col-sm-12 col-md-12 col-md-offset-0">
<label for="Disk Size">Disk Size: </label> <label for="Disk Size">Disk Size: {{vm.disk_size}} GiB</label>
<input class="form-control short-input text-center disk-space-selector" name="disk_space" type="number" id="{{vm.hosting_company}}-disk_space" min="10" value="10" step="10" data-vm-type="{{vm.hosting_company}}"/>
<span>GiB</span>
</div> </div>
</div> </div>
</li> </li>
<li> <li>
<input id="{{vm.hosting_company}}-final-price-input" type="hidden" name="final_price" value="{{vm.default_price|floatformat}}"> <label for="configuration">Configuration: </label>
<h3 id="{{vm.hosting_company}}-final-price">{{vm.default_price|floatformat}}CHF</h3> <select class="form-control" name="configuration" id="{{vm.hosting_company}}-configuration" data-vm-type="{{vm.hosting_company}}">
{% for key,value in configuration_options.items %}
<option value="{{key}}">{{ value }}</option>
{% endfor %}
</select>
</li>
<li>
<input type="hidden" name="final_price" value="{{vm.final_price|floatformat}}">
<h3 id="{{vm.hosting_company}}-final-price">{{vm.final_price|floatformat}}CHF</h3>
<span>per month</span> <span>per month</span>
</li> </li>
<li> <li>

View file

@ -65,7 +65,7 @@
{% url 'hosting:payment' as payment_url %} {% url 'hosting:payment' as payment_url %}
{% if payment_url in request.META.HTTP_REFERER %} {% if payment_url in request.META.HTTP_REFERER %}
<div class=" content pull-right"> <div class=" content pull-right">
<a href="{% url 'hosting:virtual_machine_key' order.vm_plan.id %}" ><button class="btn btn-info">{% trans "Finish Configuration"%}</button></a> <a href="{% url 'hosting:key_pair'%}" ><button class="btn btn-info">{% trans "Finish Configuration"%}</button></a>
</div> </div>
{% endif %} {% endif %}
</div> </div>

View file

@ -86,11 +86,11 @@
<h3><b>Billing Amount</b></h3> <h3><b>Billing Amount</b></h3>
<hr> <hr>
<div class="content"> <div class="content">
<p><b>Type</b> <span class="pull-right">{{request.session.vm_specs.location_code}}</span></p> <!-- <p><b>Type</b> <span class="pull-right">{{request.session.vm_specs.location_code}}</span></p> -->
<hr> <!-- <hr> -->
<p><b>Cores</b> <span class="pull-right">{{request.session.vm_specs.cores}}</span></p> <p><b>Cores</b> <span class="pull-right">{{request.session.vm_specs.cores}}</span></p>
<hr> <hr>
<p><b>Configuration</b> <span class="pull-right">{{request.session.vm_specs.configuration_detail}}</span></p> <p><b>Configuration</b> <span class="pull-right">{{request.session.vm_specs.configuration_display}}</span></p>
<hr> <hr>
<p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p> <p><b>Memory</b> <span class="pull-right">{{request.session.vm_specs.memory}} GiB</span></p>
<hr> <hr>

View file

@ -6,30 +6,75 @@
<div class="row"> <div class="row">
<div class="col-md-9 col-md-offset-2"> <div class="col-md-9 col-md-offset-2">
<div class="col-sm-12"> <div class="col-sm-12">
<form method="POST" action="" >
<h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "SSH Private Key"%} </h3> {% csrf_token %}
<h3><i class="fa fa-key" aria-hidden="true"></i>{% trans "Access Key"%} </h3>
<hr/> <hr/>
{% if not user_key %}
<div class="alert alert-warning">
{% trans "Upload your own key. "%}
</div>
<div class="form-group">
<label for="comment">Paste here your public key</label>
<textarea class="form-control" rows="6" name="public_key"></textarea>
</div>
<div class="form-group">
<button class="btn btn-success">{% trans "Upload Key"%} </a>
</div>
<div class="alert alert-warning">
{% trans "Or generate a new key pair."%}
</div>
<div class="form-group">
<button class="btn btn-success">{% trans "Generate Key Pair"%} </a>
</div>
{% else %}
<h5> Use your created key to access to the machine. If you lost it, contact us. </h5>
<table class="table borderless table-hover">
<br/>
<thead>
<tr>
<th>{% trans "Name"%}</th>
<th>{% trans "Created at"%} </th>
<th>{% trans "Status"%} </th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td scope="row">{{user_key.name}}</td>
<td>{{user_key.created_at}}</td>
<td>
<span class="h3 label label-success"><strong>Active</strong></span>
</td>
</tr>
</tbody>
</table>
{% endif %}
</form>
{% if private_key %} {% if private_key %}
<div class="alert alert-warning"> <div class="alert alert-warning">
<strong>{% trans "Warning!"%}</strong>{% trans "You can view your SSH private key once. Copy it or if it wasn't downloaded automatically, just click on Download to start it."%} <strong>{% trans "Warning!"%}</strong>{% trans "You can view your SSH private key once. Copy it or if it wasn't downloaded automatically, just click on Download to start it."%}
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="comment">private_key.pem</label> <textarea class="form-control" rows="6" id="ssh_key" type="hidden" style="display:none">{{private_key}}</textarea>
<textarea class="form-control" rows="6" id="ssh_key">{{private_key}}</textarea>
</div> </div>
<div class="form-group pull-right"> <!-- <div class="form-group pull-right">
<button type="button" id="copy_to_clipboard" data-clipboard-target="#ssh_key" class="btn btn-warning" <button type="button" id="copy_to_clipboard" data-clipboard-target="#ssh_key" class="btn btn-warning"
data-toggle="tooltip" data-placement="bottom" title="Copied" data-trigger="click">{% trans "Copy to Clipboard"%}</button> data-toggle="tooltip" data-placement="bottom" title="Copied" data-trigger="click">{% trans "Copy to Clipboard"%}</button>
<button type="button" id="download_ssh_key" class="btn btn-warning">{% trans "Download"%}</button> <button type="button" id="download_ssh_key" class="btn btn-warning">{% trans "Download"%}</button>
</div> </div> -->
{% else %} {% else %}
<div class="alert alert-warning"> <!-- <div class="alert alert-warning">
<strong>{% trans "Warning!"%}</strong>{% trans "Your SSH private key was already generated and downloaded, if you lost it, contact us. "%} <strong>{% trans "Warning!"%}</strong>{% trans "Your SSH private key was already generated and downloaded, if you lost it, contact us. "%}
</div> </div>
{% endif %} --> {% endif %}
<a class="btn btn-success" href="{% url 'hosting:virtual_machines' virtual_machine.id %}">{% trans "Go to my Virtual Machine Dashboard"%} </a> <!-- <a class="btn btn-success" href="{% url 'hosting:virtual_machines' %}">{% trans "Generate my key"%} </a> -->
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
</div> </div>
@ -43,10 +88,11 @@
<script type="text/javascript"> <script type="text/javascript">
var key = window.document.getElementById('ssh_key'); var key = window.document.getElementById('ssh_key');
var a = window.document.createElement('a'); var a = window.document.createElement('a');
a.href = window.URL.createObjectURL(new Blob(['key'], {type: 'text'})); a.href = window.URL.createObjectURL(new Blob([key.value], {type: 'text'}));
a.download = 'private_key.pem'; a.download = '{{key_name}}.pem';
// Append anchor to body. // Append anchor to body.
document.body.appendChild(a); document.body.appendChild(a);

View file

@ -6,12 +6,14 @@
<div class="row"> <div class="row">
<div class="col-md-8 col-md-offset-2"> <div class="col-md-8 col-md-offset-2">
<table class="table borderless table-hover"> <table class="table borderless table-hover">
<h3><i class="fa fa-server" aria-hidden="true"></i>{% trans "Virtual Machines"%} </h3> <h3 class="pull-left"><i class="fa fa-server" aria-hidden="true"></i> {% trans "Virtual Machines"%} </h3>
<p class="pull-right">
<a class="btn btn-success" href="{% url 'hosting:create-virtual-machine' %}" >{% trans "Create VM"%} </a>
</p>
<br/> <br/>
<thead> <thead>
<tr> <tr>
<th>{% trans "ID"%}</th> <th>{% trans "ID"%}</th>
<th>{% trans "Location"%} </th>
<th>{% trans "Amount"%}</th> <th>{% trans "Amount"%}</th>
<th>{% trans "Status"%}</th> <th>{% trans "Status"%}</th>
<th></th> <th></th>
@ -21,7 +23,6 @@
{% for vm in vms %} {% for vm in vms %}
<tr> <tr>
<td scope="row">{{vm.name}}</td> <td scope="row">{{vm.name}}</td>
<td>{{vm.location}}</td>
<td>{{vm.price}} CHF</td> <td>{{vm.price}} CHF</td>
<td> <td>

View file

@ -4,7 +4,8 @@ from .views import DjangoHostingView, RailsHostingView, PaymentVMView,\
NodeJSHostingView, LoginView, SignupView, IndexView, \ NodeJSHostingView, LoginView, SignupView, IndexView, \
OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView,\ OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView,\
VirtualMachineView, GenerateVMSSHKeysView, OrdersHostingDeleteView, NotificationsView, \ VirtualMachineView, GenerateVMSSHKeysView, OrdersHostingDeleteView, NotificationsView, \
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView,\
CreateVirtualMachinesView
urlpatterns = [ urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'), url(r'index/?$', IndexView.as_view(), name='index'),
@ -16,13 +17,14 @@ urlpatterns = [
url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'), url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'), url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'),
url(r'cancel_order/(?P<pk>\d+)/?$', OrdersHostingDeleteView.as_view(), name='delete_order'), url(r'cancel_order/(?P<pk>\d+)/?$', OrdersHostingDeleteView.as_view(), name='delete_order'),
url(r'create-virtual-machine/?$', CreateVirtualMachinesView.as_view(), name='create-virtual-machine'),
url(r'my-virtual-machines/?$', VirtualMachinesPlanListView.as_view(), name='virtual_machines'), url(r'my-virtual-machines/?$', VirtualMachinesPlanListView.as_view(), name='virtual_machines'),
url(r'my-virtual-machines/(?P<pk>\d+)/?$', VirtualMachineView.as_view(), url(r'my-virtual-machines/(?P<pk>\d+)/?$', VirtualMachineView.as_view(),
name='virtual_machines'), name='virtual_machines'),
# url(r'my-virtual-machines/(?P<pk>\d+)/delete/?$', VirtualMachineCancelView.as_view(), # url(r'my-virtual-machines/(?P<pk>\d+)/delete/?$', VirtualMachineCancelView.as_view(),
# name='virtual_machines_cancel'), # name='virtual_machines_cancel'),
url(r'my-virtual-machines/(?P<pk>\d+)/key/?$', GenerateVMSSHKeysView.as_view(), url(r'vm-key-pair/?$', GenerateVMSSHKeysView.as_view(),
name='virtual_machine_key'), name='key_pair'),
url(r'^notifications/$', NotificationsView.as_view(), name='notifications'), url(r'^notifications/$', NotificationsView.as_view(), name='notifications'),
url(r'^notifications/(?P<pk>\d+)/?$', MarkAsReadNotificationView.as_view(), url(r'^notifications/(?P<pk>\d+)/?$', MarkAsReadNotificationView.as_view(),
name='read_notification'), name='read_notification'),

View file

@ -1,3 +1,4 @@
from collections import namedtuple
from django.shortcuts import render from django.shortcuts import render
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
@ -7,6 +8,8 @@ from django.views.generic import View, CreateView, FormView, ListView, DetailVie
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
from django.conf import settings from django.conf import settings
from django.shortcuts import redirect
from guardian.mixins import PermissionRequiredMixin from guardian.mixins import PermissionRequiredMixin
from stored_messages.settings import stored_messages_settings from stored_messages.settings import stored_messages_settings
@ -14,14 +17,16 @@ from stored_messages.models import Message
from stored_messages.api import mark_read from stored_messages.api import mark_read
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.forms import BillingAddressForm, PasswordResetRequestForm from utils.forms import BillingAddressForm, PasswordResetRequestForm
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, UserHostingKey
from .forms import HostingUserSignupForm, HostingUserLoginForm from .forms import HostingUserSignupForm, HostingUserLoginForm, UserHostingKeyForm
from .mixins import ProcessVMSelectionMixin from .mixins import ProcessVMSelectionMixin
from .opennebula_functions import HostingManageVMAdmin
class DjangoHostingView(ProcessVMSelectionMixin, View): class DjangoHostingView(ProcessVMSelectionMixin, View):
@ -38,6 +43,7 @@ class DjangoHostingView(ProcessVMSelectionMixin, View):
'google_analytics': "UA-62285904-6", 'google_analytics': "UA-62285904-6",
'email': "info@django-hosting.ch", 'email': "info@django-hosting.ch",
'vm_types': VirtualMachineType.get_serialized_vm_types(), 'vm_types': VirtualMachineType.get_serialized_vm_types(),
'configuration_options': dict(VirtualMachinePlan.VM_CONFIGURATION)
} }
return context return context
@ -206,25 +212,51 @@ class MarkAsReadNotificationView(LoginRequiredMixin, UpdateView):
return HttpResponseRedirect(reverse('hosting:notifications')) return HttpResponseRedirect(reverse('hosting:notifications'))
class GenerateVMSSHKeysView(LoginRequiredMixin, DetailView): class GenerateVMSSHKeysView(LoginRequiredMixin, FormView):
model = VirtualMachinePlan form_class = UserHostingKeyForm
model = UserHostingKey
template_name = 'hosting/virtual_machine_key.html' template_name = 'hosting/virtual_machine_key.html'
success_url = reverse_lazy('hosting:orders') success_url = reverse_lazy('hosting:orders')
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
context_object_name = "virtual_machine" context_object_name = "virtual_machine"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
try:
user_key = UserHostingKey.objects.get(
user=self.request.user
)
except UserHostingKey.DoesNotExist:
user_key = None
context = super(
GenerateVMSSHKeysView,
self
).get_context_data(**kwargs)
context = super(GenerateVMSSHKeysView, self).get_context_data(**kwargs)
vm = self.get_object()
if not vm.public_key:
private_key, public_key = vm.generate_keys()
context.update({ context.update({
'private_key': private_key, 'user_key': user_key
'public_key': public_key
}) })
return context return context
return context
def get_form_kwargs(self):
kwargs = super(GenerateVMSSHKeysView, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
def form_valid(self, form):
form.save()
context = self.get_context_data()
if form.cleaned_data.get('private_key'):
context.update({
'private_key': form.cleaned_data.get('private_key'),
'key_name': form.cleaned_data.get('name')
})
# print("form", form.cleaned_data)
return render(self.request, self.template_name, context)
class PaymentVMView(LoginRequiredMixin, FormView): class PaymentVMView(LoginRequiredMixin, FormView):
@ -246,18 +278,26 @@ class PaymentVMView(LoginRequiredMixin, FormView):
if form.is_valid(): if form.is_valid():
context = self.get_context_data() context = self.get_context_data()
specifications = request.session.get('vm_specs') specifications = request.session.get('vm_specs')
vm_type = specifications.get('hosting_company')
vm = VirtualMachineType.objects.get(hosting_company=vm_type) vm_template = specifications.get('vm_template', 1)
final_price = vm.calculate_price(specifications)
vm_type = VirtualMachineType.objects.get(id=vm_template)
specs = vm_type.get_specs()
final_price = vm_type.calculate_price()
plan_data = { plan_data = {
'vm_type': vm, 'vm_type': vm_type,
'cores': specifications.get('cores'), 'configuration': specifications.get(
'memory': specifications.get('memory'), 'configuration',
'disk_size': specifications.get('disk_size'), 'django'
'configuration': specifications.get('configuration'), ),
'price': final_price 'price': final_price
} }
plan_data.update(specs)
token = form.cleaned_data.get('token') token = form.cleaned_data.get('token')
# Get or create stripe customer # Get or create stripe customer
@ -299,6 +339,20 @@ class PaymentVMView(LoginRequiredMixin, FormView):
# If the Stripe payment was successed, set order status approved # If the Stripe payment was successed, set order status approved
order.set_approved() order.set_approved()
# Create VM using oppenebula functions
# _request = namedtuple('request', 'POST user')
# _request.user = request.user
# user = namedtuple('user', 'email')
# email
# _request.POST = {
# 'vm_template': vm_template
# }
hosting_admin = HostingManageVMAdmin.__new__(HostingManageVMAdmin)
hosting_admin.init_opennebula_client(request)
oppennebula_vm_id = hosting_admin.create_vm_view(vm_type.get_specs())
plan.oppenebula_id = oppennebula_vm_id
# Send notification to ungleich as soon as VM has been booked # Send notification to ungleich as soon as VM has been booked
context = { context = {
'vm': plan, 'vm': plan,
@ -358,11 +412,48 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
ordering = '-id' ordering = '-id'
def get_queryset(self): def get_queryset(self):
hosting_admin = HostingManageVMAdmin.__new__(HostingManageVMAdmin)
print(hosting_admin.show_vms(self.request))
user = self.request.user user = self.request.user
self.queryset = VirtualMachinePlan.objects.active(user) self.queryset = VirtualMachinePlan.objects.active(user)
return super(VirtualMachinesPlanListView, self).get_queryset() return super(VirtualMachinesPlanListView, self).get_queryset()
class CreateVirtualMachinesView(LoginRequiredMixin, View):
template_name = "hosting/create_virtual_machine.html"
login_url = reverse_lazy('hosting:login')
def get(self, request, *args, **kwargs):
context = {
'vm_types': VirtualMachineType.get_serialized_vm_types(),
'configuration_options': VirtualMachinePlan.VM_CONFIGURATION
}
# context = {}
return render(request, self.template_name, context)
def post(self, request):
configuration = request.POST.get('configuration')
configuration_display = dict(VirtualMachinePlan.VM_CONFIGURATION).get(configuration)
vm_template = request.POST.get('vm_template')
vm_type = VirtualMachineType.objects.get(id=vm_template)
vm_specs = vm_type.get_specs()
vm_specs.update({
'configuration_display': configuration_display,
'configuration': configuration,
'final_price': vm_type.final_price,
'vm_template': vm_template
})
request.session['vm_specs'] = vm_specs
return redirect(reverse('hosting:payment'))
# def get_queryset(self):
# # hosting_admin = HostingManageVMAdmin.__new__(HostingManageVMAdmin)
# # print(hosting_admin.show_vms(self.request))
# user = self.request.user
# self.queryset = VirtualMachinePlan.objects.active(user)
# return super(VirtualMachinesPlanListView, self).get_queryset()
class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, UpdateView): class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, UpdateView):
template_name = "hosting/virtual_machine_detail.html" template_name = "hosting/virtual_machine_detail.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')