Added status field to a booked VM. Added the capability to change a booked VM status from the admin panel. Send email to user after his VM is approved
This commit is contained in:
parent
081370c062
commit
7a9ea58a89
14 changed files with 177 additions and 15 deletions
|
@ -1,6 +1,32 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from utils.mailer import BaseEmail
|
||||||
from .models import VirtualMachineType, VirtualMachinePlan
|
from .models import VirtualMachineType, VirtualMachinePlan
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualMachinePlanAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'id', 'email')
|
||||||
|
|
||||||
|
def email(self, obj):
|
||||||
|
return obj.hosting_orders.latest('id').customer.user.email
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
email = self.email(obj)
|
||||||
|
if 'status' in form.changed_data and obj.status == VirtualMachinePlan.ONLINE_STATUS:
|
||||||
|
context = {
|
||||||
|
'vm': obj
|
||||||
|
}
|
||||||
|
email_data = {
|
||||||
|
'subject': 'Your VM has been activated',
|
||||||
|
'to': email,
|
||||||
|
'context': context,
|
||||||
|
'template_name': 'vm_activated',
|
||||||
|
'template_path': 'emails/'
|
||||||
|
}
|
||||||
|
email = BaseEmail(**email_data)
|
||||||
|
email.send()
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(VirtualMachineType)
|
admin.site.register(VirtualMachineType)
|
||||||
admin.site.register(VirtualMachinePlan)
|
admin.site.register(VirtualMachinePlan, VirtualMachinePlanAdmin)
|
||||||
|
|
20
hosting/migrations/0019_virtualmachineplan_status.py
Normal file
20
hosting/migrations/0019_virtualmachineplan_status.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.4 on 2016-05-26 02:57
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hosting', '0018_virtualmachineplan_public_key'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='virtualmachineplan',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('pending', 'Pending for activation'), ('online', 'Online')], default='online', max_length=20),
|
||||||
|
),
|
||||||
|
]
|
20
hosting/migrations/0020_auto_20160526_0258.py
Normal file
20
hosting/migrations/0020_auto_20160526_0258.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.4 on 2016-05-26 02:58
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hosting', '0019_virtualmachineplan_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='virtualmachineplan',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('pending', 'Pending for activation'), ('online', 'Online')], default='pending', max_length=20),
|
||||||
|
),
|
||||||
|
]
|
20
hosting/migrations/0021_auto_20160526_0445.py
Normal file
20
hosting/migrations/0021_auto_20160526_0445.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.4 on 2016-05-26 04:45
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hosting', '0020_auto_20160526_0258'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='virtualmachineplan',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('pending', 'Pending for activation'), ('online', 'Online'), ('canceled', 'Canceled')], default='pending', max_length=20),
|
||||||
|
),
|
||||||
|
]
|
|
@ -81,12 +81,24 @@ class VirtualMachineType(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class VirtualMachinePlan(models.Model):
|
class VirtualMachinePlan(models.Model):
|
||||||
|
|
||||||
|
PENDING_STATUS = 'pending'
|
||||||
|
ONLINE_STATUS = 'online'
|
||||||
|
CANCELED_STATUS = 'canceled'
|
||||||
|
|
||||||
|
VM_STATUS_CHOICES = (
|
||||||
|
(PENDING_STATUS, 'Pending for activation'),
|
||||||
|
(ONLINE_STATUS, 'Online'),
|
||||||
|
(CANCELED_STATUS, 'Canceled')
|
||||||
|
)
|
||||||
|
|
||||||
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)
|
||||||
price = models.FloatField()
|
price = models.FloatField()
|
||||||
public_key = models.TextField()
|
public_key = models.TextField()
|
||||||
|
status = models.CharField(max_length=20, choices=VM_STATUS_CHOICES, default=PENDING_STATUS)
|
||||||
|
|
||||||
objects = VMPlansManager()
|
objects = VMPlansManager()
|
||||||
|
|
||||||
|
@ -97,6 +109,10 @@ class VirtualMachinePlan(models.Model):
|
||||||
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
|
||||||
|
def location(self):
|
||||||
|
return self.vm_type.get_location_display()
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def name(self):
|
def name(self):
|
||||||
name = 'vm-%s' % self.id
|
name = 'vm-%s' % self.id
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
<<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title></title>
|
<title></title>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
<<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title></title>
|
<title></title>
|
||||||
|
|
13
hosting/templates/emails/vm_activated.html
Normal file
13
hosting/templates/emails/vm_activated.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
{% load staticfiles bootstrap3%}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
You virtual machine {{vm.name}} has been activated. You can manage your vm on this <a href="{{request.HOS}}{% url 'hosting:virtual_machines' vm.id %}"> link </a>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
15
hosting/templates/emails/vm_activated.txt
Normal file
15
hosting/templates/emails/vm_activated.txt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
{% load staticfiles bootstrap3%}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
You virtual machine {{vm.name}} has been activated. You can manage your vm in this <a href="{% url 'hosting:virtual_machines' vm.id %}"> link </a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -122,12 +122,16 @@
|
||||||
</div><!--/row-->
|
</div><!--/row-->
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="status-v">
|
<div class="tab-pane" id="status-v">
|
||||||
|
|
||||||
<div class="row ">
|
<div class="row ">
|
||||||
<div class="col-md-12 inline-headers">
|
<div class="col-md-12 inline-headers">
|
||||||
<h3>Current status</h3>
|
<h3>Current status</h3>
|
||||||
<span class="h3 pull-right label label-success"><strong>Online</strong></span>
|
{% if virtual_machine.status == 'pending' %}
|
||||||
<hr>
|
<span class="h3 pull-right label label-warning"><strong>{{virtual_machine.get_status_display}}</strong></span>
|
||||||
|
{% elif virtual_machine.status == 'online' %}
|
||||||
|
<span class="h3 pull-right label label-success"><strong>{{virtual_machine.get_status_display}}</strong></span>
|
||||||
|
{% else %}
|
||||||
|
<span class="h3 pull-right label label-error"><strong>{{virtual_machine.get_status_display}}</strong></span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>Type</th>
|
<th>Location</th>
|
||||||
<th>Amount</th>
|
<th>Amount</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
{% for vm in vms %}
|
{% for vm in vms %}
|
||||||
<tr>
|
<tr>
|
||||||
<td scope="row">{{vm.name}}</td>
|
<td scope="row">{{vm.name}}</td>
|
||||||
<td>{{vm.hosting_company_name}}</td>
|
<td>{{vm.location}}</td>
|
||||||
<td>{{vm.price}} CHF</td>
|
<td>{{vm.price}} CHF</td>
|
||||||
<td>
|
<td>
|
||||||
<button type="button" class="btn btn-default"><a href="{% url 'hosting:virtual_machines' vm.id %}">View Detail</a></button>
|
<button type="button" class="btn btn-default"><a href="{% url 'hosting:virtual_machines' vm.id %}">View Detail</a></button>
|
||||||
|
|
31
membership/migrations/0006_auto_20160526_0445.py
Normal file
31
membership/migrations/0006_auto_20160526_0445.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.4 on 2016-05-26 04:45
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0007_alter_validators_add_error_messages'),
|
||||||
|
('membership', '0005_customuser_is_admin'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customuser',
|
||||||
|
name='groups',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customuser',
|
||||||
|
name='is_superuser',
|
||||||
|
field=models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customuser',
|
||||||
|
name='user_permissions',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -2,7 +2,7 @@ from datetime import datetime
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.contrib.auth.models import User, AbstractBaseUser, BaseUserManager, AbstractUser
|
from django.contrib.auth.models import User, AbstractBaseUser, BaseUserManager, AbstractUser, PermissionsMixin
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
@ -47,7 +47,7 @@ class MyUserManager(BaseUserManager):
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
class CustomUser(AbstractBaseUser):
|
class CustomUser(AbstractBaseUser, PermissionsMixin):
|
||||||
VALIDATED_CHOICES = ((0, 'Not validated'), (1, 'Validated'))
|
VALIDATED_CHOICES = ((0, 'Not validated'), (1, 'Validated'))
|
||||||
site = models.ForeignKey(Site, default=1)
|
site = models.ForeignKey(Site, default=1)
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
|
|
|
@ -16,11 +16,8 @@ class BaseEmail(object):
|
||||||
self.subject = kwargs.get('subject')
|
self.subject = kwargs.get('subject')
|
||||||
self.context = kwargs.get('context', {})
|
self.context = kwargs.get('context', {})
|
||||||
self.template_full_path = '%s%s' % (self.template_path, self.template_name)
|
self.template_full_path = '%s%s' % (self.template_path, self.template_name)
|
||||||
|
text_content = render_to_string('%s.txt' % self.template_full_path, self.context)
|
||||||
text_content = render_to_string('%s.txt' % self.template_full_path,
|
html_content = render_to_string('%s.html' % self.template_full_path, self.context)
|
||||||
{'data': self.context})
|
|
||||||
html_content = render_to_string('%s.html' % self.template_full_path,
|
|
||||||
{'data': self.context})
|
|
||||||
|
|
||||||
self.email = EmailMultiAlternatives(self.subject, text_content)
|
self.email = EmailMultiAlternatives(self.subject, text_content)
|
||||||
self.email.attach_alternative(html_content, "text/html")
|
self.email.attach_alternative(html_content, "text/html")
|
||||||
|
|
Loading…
Reference in a new issue