From bd875ffe7deb21c8d99eaa0501a43ef75285141d Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Wed, 18 Apr 2018 23:50:52 +0200
Subject: [PATCH 01/20] Create hostingorder outside celery task

---
 datacenterlight/tasks.py | 69 +++++++++++++++-------------------------
 datacenterlight/tests.py | 11 +++++--
 datacenterlight/views.py | 52 +++++++++++++++++++++++++++---
 hosting/views.py         | 48 ++++++++++++++++++++++++++--
 4 files changed, 126 insertions(+), 54 deletions(-)

diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py
index 3db6eb54..67b00c5d 100644
--- a/datacenterlight/tasks.py
+++ b/datacenterlight/tasks.py
@@ -49,24 +49,11 @@ def retry_task(task, exception=None):
 
 
 @app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES)
-def create_vm_task(self, vm_template_id, user, specs, template,
-                   stripe_customer_id, billing_address_data,
-                   stripe_subscription_id, cc_details):
+def create_vm_task(self, vm_template_id, user, specs, template, order_id):
     logger.debug(
         "Running create_vm_task on {}".format(current_task.request.hostname))
     vm_id = None
     try:
-        final_price = specs.get('price')
-        billing_address = BillingAddress(
-            cardholder_name=billing_address_data['cardholder_name'],
-            street_address=billing_address_data['street_address'],
-            city=billing_address_data['city'],
-            postal_code=billing_address_data['postal_code'],
-            country=billing_address_data['country']
-        )
-        billing_address.save()
-        customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
-
         if 'pass' in user:
             on_user = user.get('email')
             on_pass = user.get('pass')
@@ -94,33 +81,26 @@ def create_vm_task(self, vm_template_id, user, specs, template,
         if vm_id is None:
             raise Exception("Could not create VM")
 
-        # Create a Hosting Order
-        order = HostingOrder.create(
-            price=final_price,
-            vm_id=vm_id,
-            customer=customer,
-            billing_address=billing_address
-        )
-
-        # Create a Hosting Bill
-        HostingBill.create(
-            customer=customer, billing_address=billing_address)
-
-        # Create Billing Address for User if he does not have one
-        if not customer.user.billing_addresses.count():
-            billing_address_data.update({
-                'user': customer.user.id
-            })
-            billing_address_user_form = UserBillingAddressForm(
-                billing_address_data)
-            billing_address_user_form.is_valid()
-            billing_address_user_form.save()
-
-        # Associate an order with a stripe subscription
-        order.set_subscription_id(stripe_subscription_id, cc_details)
-
-        # If the Stripe payment succeeds, set order status approved
-        order.set_approved()
+        # Update HostingOrder with the created vm_id
+        hosting_order = HostingOrder.objects.filter(id=order_id).first()
+        error_msg = None
+        if hosting_order:
+            logger.debug(
+                "Updating hosting_order {} with vm_id={}".format(
+                    hosting_order.id, vm_id
+                )
+            )
+            hosting_order.vm_id = vm_id
+            hosting_order.save()
+        else:
+            error_msg = (
+                "HostingOrder with id {order_id} not found. This means that "
+                "the hosting order was not created and/or it is/was not "
+                "associated with VM with id {vm_id}".format(
+                    order_id=order_id, vm_id=vm_id
+                )
+            )
+            logger.error(error_msg)
 
         vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
 
@@ -134,8 +114,11 @@ def create_vm_task(self, vm_template_id, user, specs, template,
             'template': template.get('name'),
             'vm_name': vm.get('name'),
             'vm_id': vm['vm_id'],
-            'order_id': order.id
+            'order_id': order_id
         }
+
+        if error_msg:
+            context['errors'] = error_msg
         email_data = {
             'subject': settings.DCL_TEXT + " Order from %s" % context['email'],
             'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
@@ -159,7 +142,7 @@ def create_vm_task(self, vm_template_id, user, specs, template,
                 'base_url': "{0}://{1}".format(user.get('request_scheme'),
                                                user.get('request_host')),
                 'order_url': reverse('hosting:orders',
-                                     kwargs={'pk': order.id}),
+                                     kwargs={'pk': order_id}),
                 'page_header': _(
                     'Your New VM %(vm_name)s at Data Center Light') % {
                     'vm_name': vm.get('name')},
diff --git a/datacenterlight/tests.py b/datacenterlight/tests.py
index d1ce9785..25c0bf4c 100644
--- a/datacenterlight/tests.py
+++ b/datacenterlight/tests.py
@@ -122,10 +122,15 @@ class CeleryTaskTestCase(TestCase):
             msg = subscription_result.get('error')
             raise Exception("Creating subscription failed: {}".format(msg))
 
+        order = HostingOrder.create(
+            price=specs['price'],
+            vm_id=0,
+            customer=customer,
+            billing_address=billing_address
+        )
+
         async_task = create_vm_task.delay(
-            vm_template_id, self.user, specs, template_data,
-            stripe_customer.id, billing_address_data,
-            stripe_subscription_obj.id, card_details_dict
+            vm_template_id, self.user, specs, template_data, order.id
         )
         new_vm_id = 0
         res = None
diff --git a/datacenterlight/views.py b/datacenterlight/views.py
index af3b774c..a855b3cc 100644
--- a/datacenterlight/views.py
+++ b/datacenterlight/views.py
@@ -15,11 +15,14 @@ from django.views.generic import FormView, CreateView, DetailView
 
 from datacenterlight.tasks import create_vm_task
 from hosting.forms import HostingUserLoginForm
-from hosting.models import HostingOrder
+from hosting.models import HostingOrder, HostingBill
 from membership.models import CustomUser, StripeCustomer
 from opennebula_api.serializers import VMTemplateSerializer
-from utils.forms import BillingAddressForm, BillingAddressFormSignup
+from utils.forms import (
+    BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm
+)
 from utils.hosting_utils import get_vm_price
+from utils.models import BillingAddress
 from utils.stripe_utils import StripeUtils
 from utils.tasks import send_plain_email_task
 from .forms import ContactForm
@@ -484,9 +487,48 @@ class OrderConfirmationView(DetailView):
             'language': get_language(),
         }
 
-        create_vm_task.delay(vm_template_id, user, specs, template,
-                             stripe_customer_id, billing_address_data,
-                             stripe_subscription_obj.id, card_details_dict)
+        billing_address = BillingAddress(
+            cardholder_name=billing_address_data['cardholder_name'],
+            street_address=billing_address_data['street_address'],
+            city=billing_address_data['city'],
+            postal_code=billing_address_data['postal_code'],
+            country=billing_address_data['country']
+        )
+        billing_address.save()
+
+        customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
+
+        # Create a Hosting Order with vm_id = 0, we shall set it later in
+        # celery task once the VM instance is up and running
+        order = HostingOrder.create(
+            price=specs['price'],
+            vm_id=0,
+            customer=customer,
+            billing_address=billing_address
+        )
+
+        # Create a Hosting Bill
+        HostingBill.create(customer=customer, billing_address=billing_address)
+
+        # Create Billing Address for User if he does not have one
+        if not customer.user.billing_addresses.count():
+            billing_address_data.update({
+                'user': customer.user.id
+            })
+            billing_address_user_form = UserBillingAddressForm(
+                billing_address_data)
+            billing_address_user_form.is_valid()
+            billing_address_user_form.save()
+
+        # Associate an order with a stripe subscription
+        order.set_subscription_id(
+            stripe_subscription_obj.id, card_details_dict
+        )
+
+        # If the Stripe payment succeeds, set order status approved
+        order.set_approved()
+
+        create_vm_task.delay(vm_template_id, user, specs, template, order.id)
         for session_var in ['specs', 'template', 'billing_address',
                             'billing_address_data',
                             'token', 'customer']:
diff --git a/hosting/views.py b/hosting/views.py
index 6e143760..c7c7655a 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -44,6 +44,7 @@ from utils.forms import (
 )
 from utils.hosting_utils import get_vm_price
 from utils.mailer import BaseEmail
+from utils.models import BillingAddress
 from utils.stripe_utils import StripeUtils
 from utils.tasks import send_plain_email_task
 from utils.views import (
@@ -882,9 +883,50 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
             'request_host': request.get_host(),
             'language': get_language(),
         }
-        create_vm_task.delay(vm_template_id, user, specs, template,
-                             stripe_customer_id, billing_address_data,
-                             stripe_subscription_obj.id, card_details_dict)
+
+        billing_address = BillingAddress(
+            cardholder_name=billing_address_data['cardholder_name'],
+            street_address=billing_address_data['street_address'],
+            city=billing_address_data['city'],
+            postal_code=billing_address_data['postal_code'],
+            country=billing_address_data['country']
+        )
+        billing_address.save()
+
+        customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
+
+        # Create a Hosting Order with vm_id = 0, we shall set it later in
+        # celery task once the VM instance is up and running
+        order = HostingOrder.create(
+            price=specs['price'],
+            vm_id=0,
+            customer=customer,
+            billing_address=billing_address
+        )
+
+        # Create a Hosting Bill
+        HostingBill.create(customer=customer, billing_address=billing_address)
+
+        # Create Billing Address for User if he does not have one
+        if not customer.user.billing_addresses.count():
+            billing_address_data.update({
+                'user': customer.user.id
+            })
+            billing_address_user_form = UserBillingAddressForm(
+                billing_address_data
+            )
+            billing_address_user_form.is_valid()
+            billing_address_user_form.save()
+
+        # Associate an order with a stripe subscription
+        order.set_subscription_id(
+            stripe_subscription_obj.id, card_details_dict
+        )
+
+        # If the Stripe payment succeeds, set order status approved
+        order.set_approved()
+
+        create_vm_task.delay(vm_template_id, user, specs, template, order.id)
 
         for session_var in ['specs', 'template', 'billing_address',
                             'billing_address_data',

From 1a7412f8ff3bda668adaa0bc74d78e50b6c40c9a Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 19 Apr 2018 00:51:55 +0200
Subject: [PATCH 02/20] stripe_utils: Add set_subscription_metadata method

---
 utils/stripe_utils.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index 79bca243..3809e138 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -233,6 +233,12 @@ class StripeUtils(object):
         )
         return subscription_result
 
+    @handleStripeError
+    def set_subscription_metadata(self, subscription_id, metadata):
+        subscription = stripe.Subscription.retrieve(subscription_id)
+        subscription.metadata = metadata
+        subscription.save()
+
     @handleStripeError
     def unsubscribe_customer(self, subscription_id):
         """

From 8a659c153e85e9a1a4f43cc761617e7c79d2aa93 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 19 Apr 2018 00:52:38 +0200
Subject: [PATCH 03/20] Set VM_ID metadata to the created subscription

---
 datacenterlight/tasks.py | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py
index 67b00c5d..8c63c84c 100644
--- a/datacenterlight/tasks.py
+++ b/datacenterlight/tasks.py
@@ -18,6 +18,7 @@ from utils.hosting_utils import get_all_public_keys, get_or_create_vm_detail
 from utils.forms import UserBillingAddressForm
 from utils.mailer import BaseEmail
 from utils.models import BillingAddress
+from utils.stripe_utils import StripeUtils
 
 logger = get_task_logger(__name__)
 
@@ -102,6 +103,22 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
             )
             logger.error(error_msg)
 
+        stripe_utils = StripeUtils()
+        result = stripe_utils.set_subscription_metadata(
+            subscription_id=hosting_order.subscription_id,
+            metadata={"VM_ID": str(vm_id)}
+        )
+        stripe_subscription_obj = result.get('response_object')
+        if stripe_subscription_obj is not None:
+            emsg = "Could not update subscription metadata for {sub}".format(
+                    sub=hosting_order.subscription_id
+                )
+            logger.error(emsg)
+            if error_msg:
+                error_msg += emsg
+            else:
+                error_msg = emsg
+
         vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
 
         context = {

From 791f48513af985a617250c10788e571325ef1d4a Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 19 Apr 2018 01:07:59 +0200
Subject: [PATCH 04/20] Organize imports

---
 datacenterlight/tasks.py | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py
index 8c63c84c..758778b6 100644
--- a/datacenterlight/tasks.py
+++ b/datacenterlight/tasks.py
@@ -10,14 +10,12 @@ from django.utils import translation
 from django.utils.translation import ugettext_lazy as _
 
 from dynamicweb.celery import app
-from hosting.models import HostingOrder, HostingBill
-from membership.models import StripeCustomer, CustomUser
+from hosting.models import HostingOrder
+from membership.models import CustomUser
 from opennebula_api.models import OpenNebulaManager
 from opennebula_api.serializers import VirtualMachineSerializer
 from utils.hosting_utils import get_all_public_keys, get_or_create_vm_detail
-from utils.forms import UserBillingAddressForm
 from utils.mailer import BaseEmail
-from utils.models import BillingAddress
 from utils.stripe_utils import StripeUtils
 
 logger = get_task_logger(__name__)

From a3a8227007ac517a08fe856bc4a038d9e016e4f7 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 19 Apr 2018 01:08:52 +0200
Subject: [PATCH 05/20] Check if subscription metadata update response does not
 have errors

---
 datacenterlight/tasks.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py
index 758778b6..12fabc1e 100644
--- a/datacenterlight/tasks.py
+++ b/datacenterlight/tasks.py
@@ -106,8 +106,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
             subscription_id=hosting_order.subscription_id,
             metadata={"VM_ID": str(vm_id)}
         )
-        stripe_subscription_obj = result.get('response_object')
-        if stripe_subscription_obj is not None:
+
+        if result.get('error') is not None:
             emsg = "Could not update subscription metadata for {sub}".format(
                     sub=hosting_order.subscription_id
                 )

From 80c3ac5346a449bc4d7f2863a818c5c8542250c0 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Thu, 19 Apr 2018 01:09:25 +0200
Subject: [PATCH 06/20] Improve create_vm_task test

---
 datacenterlight/tests.py | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/datacenterlight/tests.py b/datacenterlight/tests.py
index 25c0bf4c..d6cd6adf 100644
--- a/datacenterlight/tests.py
+++ b/datacenterlight/tests.py
@@ -12,9 +12,11 @@ from unittest import skipIf
 
 from datacenterlight.models import VMTemplate
 from datacenterlight.tasks import create_vm_task
+from hosting.models import HostingOrder
 from membership.models import StripeCustomer
 from opennebula_api.serializers import VMTemplateSerializer
 from utils.hosting_utils import get_vm_price
+from utils.models import BillingAddress
 from utils.stripe_utils import StripeUtils
 
 
@@ -81,11 +83,14 @@ class CeleryTaskTestCase(TestCase):
 
         stripe_customer = StripeCustomer.get_or_create(
             email=self.customer_email,
-            token=self.token)
+            token=self.token
+        )
         card_details = self.stripe_utils.get_card_details(
             stripe_customer.stripe_id,
-            self.token)
-        card_details_dict = card_details.get('response_object')
+            self.token
+        )
+        card_details_dict = card_details.get('error')
+        self.assertEquals(card_details_dict, None)
         billing_address_data = {'cardholder_name': self.customer_name,
                                 'postal_code': '1231',
                                 'country': 'CH',
@@ -122,10 +127,19 @@ class CeleryTaskTestCase(TestCase):
             msg = subscription_result.get('error')
             raise Exception("Creating subscription failed: {}".format(msg))
 
+        billing_address = BillingAddress(
+            cardholder_name=billing_address_data['cardholder_name'],
+            street_address=billing_address_data['street_address'],
+            city=billing_address_data['city'],
+            postal_code=billing_address_data['postal_code'],
+            country=billing_address_data['country']
+        )
+        billing_address.save()
+
         order = HostingOrder.create(
             price=specs['price'],
             vm_id=0,
-            customer=customer,
+            customer=stripe_customer,
             billing_address=billing_address
         )
 

From fae1c7fbeb74a634fbc0618228b190f4535277b4 Mon Sep 17 00:00:00 2001
From: "M.Ravi" <mondi.ravi@gmail.com>
Date: Thu, 19 Apr 2018 07:26:34 +0200
Subject: [PATCH 07/20] Update hosting_order in a pythonic way

---
 datacenterlight/tasks.py | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py
index 12fabc1e..815d627d 100644
--- a/datacenterlight/tasks.py
+++ b/datacenterlight/tasks.py
@@ -83,20 +83,21 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
         # Update HostingOrder with the created vm_id
         hosting_order = HostingOrder.objects.filter(id=order_id).first()
         error_msg = None
-        if hosting_order:
+
+        try:
+            hosting_order.vm_id = vm_id
+            hosting_order.save()
             logger.debug(
-                "Updating hosting_order {} with vm_id={}".format(
+                "Updated hosting_order {} with vm_id={}".format(
                     hosting_order.id, vm_id
                 )
             )
-            hosting_order.vm_id = vm_id
-            hosting_order.save()
-        else:
+        except Exception as ex:
             error_msg = (
                 "HostingOrder with id {order_id} not found. This means that "
                 "the hosting order was not created and/or it is/was not "
-                "associated with VM with id {vm_id}".format(
-                    order_id=order_id, vm_id=vm_id
+                "associated with VM with id {vm_id}. Details {details}".format(
+                    order_id=order_id, vm_id=vm_id, details=str(ex)
                 )
             )
             logger.error(error_msg)

From 3debf3411858df959c96362ecf9a073649464583 Mon Sep 17 00:00:00 2001
From: Arvind Tiwari <tiwariav@gmail.com>
Date: Fri, 20 Apr 2018 20:25:24 +0530
Subject: [PATCH 08/20] remove vm creation to util function

---
 datacenterlight/utils.py | 59 ++++++++++++++++++++++++++++++
 datacenterlight/views.py | 64 ++++++---------------------------
 hosting/views.py         | 77 +++++++---------------------------------
 3 files changed, 82 insertions(+), 118 deletions(-)

diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py
index 2efade8e..6c7a25bd 100644
--- a/datacenterlight/utils.py
+++ b/datacenterlight/utils.py
@@ -1,5 +1,10 @@
 from django.contrib.sites.models import Site
 
+from datacenterlight.tasks import create_vm_task
+from hosting.models import HostingOrder, HostingBill
+from membership.models import StripeCustomer
+from utils.forms import UserBillingAddressForm
+from utils.models import BillingAddress
 from .cms_models import CMSIntegration
 
 
@@ -12,3 +17,57 @@ def get_cms_integration(name):
     except CMSIntegration.DoesNotExist:
         cms_integration = CMSIntegration.objects.get(name=name, domain=None)
     return cms_integration
+
+
+def create_vm(billing_address_data, stripe_customer_id, specs,
+              stripe_subscription_obj, card_details_dict, request,
+              vm_template_id, template, user):
+    billing_address = BillingAddress(
+        cardholder_name=billing_address_data['cardholder_name'],
+        street_address=billing_address_data['street_address'],
+        city=billing_address_data['city'],
+        postal_code=billing_address_data['postal_code'],
+        country=billing_address_data['country']
+    )
+    billing_address.save()
+
+    customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
+
+    # Create a Hosting Order with vm_id = 0, we shall set it later in
+    # celery task once the VM instance is up and running
+    order = HostingOrder.create(
+        price=specs['price'],
+        vm_id=0,
+        customer=customer,
+        billing_address=billing_address
+    )
+
+    # Create a Hosting Bill
+    HostingBill.create(customer=customer, billing_address=billing_address)
+
+    # Create Billing Address for User if he does not have one
+    if not customer.user.billing_addresses.count():
+        billing_address_data.update({
+            'user': customer.user.id
+        })
+        billing_address_user_form = UserBillingAddressForm(
+            billing_address_data
+        )
+        billing_address_user_form.is_valid()
+        billing_address_user_form.save()
+
+    # Associate an order with a stripe subscription
+    order.set_subscription_id(
+        stripe_subscription_obj.id, card_details_dict
+    )
+
+    # If the Stripe payment succeeds, set order status approved
+    order.set_approved()
+
+    create_vm_task.delay(vm_template_id, user, specs, template, order.id)
+
+    for session_var in ['specs', 'template', 'billing_address',
+                        'billing_address_data',
+                        'token', 'customer']:
+        if session_var in request.session:
+            del request.session[session_var]
diff --git a/datacenterlight/views.py b/datacenterlight/views.py
index a855b3cc..203846fb 100644
--- a/datacenterlight/views.py
+++ b/datacenterlight/views.py
@@ -7,7 +7,7 @@ from django.contrib import messages
 from django.contrib.auth import login, authenticate
 from django.core.exceptions import ValidationError
 from django.core.urlresolvers import reverse
-from django.http import HttpResponseRedirect, HttpResponse
+from django.http import HttpResponseRedirect, HttpResponse, JsonResponse
 from django.shortcuts import render
 from django.utils.translation import get_language, ugettext_lazy as _
 from django.views.decorators.cache import cache_control
@@ -27,7 +27,7 @@ from utils.stripe_utils import StripeUtils
 from utils.tasks import send_plain_email_task
 from .forms import ContactForm
 from .models import VMTemplate
-from .utils import get_cms_integration
+from .utils import get_cms_integration, create_vm
 
 logger = logging.getLogger(__name__)
 
@@ -390,8 +390,8 @@ class OrderConfirmationView(DetailView):
                       ' On close of this popup, you will be redirected back to'
                       ' the payment page.'))
             }
-            return HttpResponse(json.dumps(response),
-                                content_type="application/json")
+            return JsonResponse(response)
+
         card_details_dict = card_details.get('response_object')
         cpu = specs.get('cpu')
         memory = specs.get('memory')
@@ -431,8 +431,7 @@ class OrderConfirmationView(DetailView):
                       ' On close of this popup, you will be redirected back to'
                       ' the payment page.'))
             }
-            return HttpResponse(json.dumps(response),
-                                content_type="application/json")
+            return JsonResponse(response)
 
         # Create user if the user is not logged in and if he is not already
         # registered
@@ -487,53 +486,11 @@ class OrderConfirmationView(DetailView):
             'language': get_language(),
         }
 
-        billing_address = BillingAddress(
-            cardholder_name=billing_address_data['cardholder_name'],
-            street_address=billing_address_data['street_address'],
-            city=billing_address_data['city'],
-            postal_code=billing_address_data['postal_code'],
-            country=billing_address_data['country']
+        create_vm(
+            billing_address_data, stripe_customer_id, specs,
+            stripe_subscription_obj, card_details_dict, request,
+            vm_template_id, template, user
         )
-        billing_address.save()
-
-        customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
-
-        # Create a Hosting Order with vm_id = 0, we shall set it later in
-        # celery task once the VM instance is up and running
-        order = HostingOrder.create(
-            price=specs['price'],
-            vm_id=0,
-            customer=customer,
-            billing_address=billing_address
-        )
-
-        # Create a Hosting Bill
-        HostingBill.create(customer=customer, billing_address=billing_address)
-
-        # Create Billing Address for User if he does not have one
-        if not customer.user.billing_addresses.count():
-            billing_address_data.update({
-                'user': customer.user.id
-            })
-            billing_address_user_form = UserBillingAddressForm(
-                billing_address_data)
-            billing_address_user_form.is_valid()
-            billing_address_user_form.save()
-
-        # Associate an order with a stripe subscription
-        order.set_subscription_id(
-            stripe_subscription_obj.id, card_details_dict
-        )
-
-        # If the Stripe payment succeeds, set order status approved
-        order.set_approved()
-
-        create_vm_task.delay(vm_template_id, user, specs, template, order.id)
-        for session_var in ['specs', 'template', 'billing_address',
-                            'billing_address_data',
-                            'token', 'customer']:
-            if session_var in request.session:
-                del request.session[session_var]
 
         response = {
             'status': True,
@@ -549,5 +506,4 @@ class OrderConfirmationView(DetailView):
                   ' it is ready.'))
         }
 
-        return HttpResponse(json.dumps(response),
-                            content_type="application/json")
+        return JsonResponse(response)
diff --git a/hosting/views.py b/hosting/views.py
index c7c7655a..5d5051e4 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1,4 +1,3 @@
-import json
 import logging
 import uuid
 from datetime import datetime
@@ -12,7 +11,9 @@ from django.contrib.auth.tokens import default_token_generator
 from django.core.exceptions import ValidationError
 from django.core.files.base import ContentFile
 from django.core.urlresolvers import reverse_lazy, reverse
-from django.http import Http404, HttpResponseRedirect, HttpResponse
+from django.http import (
+    Http404, HttpResponseRedirect, HttpResponse, JsonResponse
+)
 from django.shortcuts import redirect, render
 from django.utils.http import urlsafe_base64_decode
 from django.utils.safestring import mark_safe
@@ -31,7 +32,7 @@ from stored_messages.models import Message
 from stored_messages.settings import stored_messages_settings
 
 from datacenterlight.models import VMTemplate
-from datacenterlight.tasks import create_vm_task
+from datacenterlight.utils import create_vm
 from membership.models import CustomUser, StripeCustomer
 from opennebula_api.models import OpenNebulaManager
 from opennebula_api.serializers import (
@@ -44,7 +45,6 @@ from utils.forms import (
 )
 from utils.hosting_utils import get_vm_price
 from utils.mailer import BaseEmail
-from utils.models import BillingAddress
 from utils.stripe_utils import StripeUtils
 from utils.tasks import send_plain_email_task
 from utils.views import (
@@ -873,8 +873,8 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
                       ' On close of this popup, you will be redirected back to'
                       ' the payment page.'))
             }
-            return HttpResponse(json.dumps(response),
-                                content_type="application/json")
+            return JsonResponse(response)
+
         user = {
             'name': self.request.user.name,
             'email': self.request.user.email,
@@ -884,55 +884,11 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
             'language': get_language(),
         }
 
-        billing_address = BillingAddress(
-            cardholder_name=billing_address_data['cardholder_name'],
-            street_address=billing_address_data['street_address'],
-            city=billing_address_data['city'],
-            postal_code=billing_address_data['postal_code'],
-            country=billing_address_data['country']
+        create_vm(
+            billing_address_data, stripe_customer_id, specs,
+            stripe_subscription_obj, card_details_dict, request,
+            vm_template_id, template, user
         )
-        billing_address.save()
-
-        customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
-
-        # Create a Hosting Order with vm_id = 0, we shall set it later in
-        # celery task once the VM instance is up and running
-        order = HostingOrder.create(
-            price=specs['price'],
-            vm_id=0,
-            customer=customer,
-            billing_address=billing_address
-        )
-
-        # Create a Hosting Bill
-        HostingBill.create(customer=customer, billing_address=billing_address)
-
-        # Create Billing Address for User if he does not have one
-        if not customer.user.billing_addresses.count():
-            billing_address_data.update({
-                'user': customer.user.id
-            })
-            billing_address_user_form = UserBillingAddressForm(
-                billing_address_data
-            )
-            billing_address_user_form.is_valid()
-            billing_address_user_form.save()
-
-        # Associate an order with a stripe subscription
-        order.set_subscription_id(
-            stripe_subscription_obj.id, card_details_dict
-        )
-
-        # If the Stripe payment succeeds, set order status approved
-        order.set_approved()
-
-        create_vm_task.delay(vm_template_id, user, specs, template, order.id)
-
-        for session_var in ['specs', 'template', 'billing_address',
-                            'billing_address_data',
-                            'token', 'customer']:
-            if session_var in request.session:
-                del request.session[session_var]
 
         response = {
             'status': True,
@@ -944,8 +900,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
                   ' it is ready.'))
         }
 
-        return HttpResponse(json.dumps(response),
-                            content_type="application/json")
+        return JsonResponse(response)
 
 
 class OrdersHostingListView(LoginRequiredMixin, ListView):
@@ -1128,10 +1083,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
                 for m in storage:
                     pass
                 storage.used = True
-                return HttpResponse(
-                    json.dumps({'text': ugettext('Terminated')}),
-                    content_type="application/json"
-                )
+                return JsonResponse({'text': ugettext('Terminated')})
             else:
                 return redirect(reverse('hosting:virtual_machines'))
         elif self.request.is_ajax():
@@ -1262,10 +1214,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
                 ["%s=%s" % (k, v) for (k, v) in admin_email_body.items()]),
         }
         send_plain_email_task.delay(email_to_admin_data)
-        return HttpResponse(
-            json.dumps(response),
-            content_type="application/json"
-        )
+        return JsonResponse(response)
 
 
 class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin,

From 736253fedacd424eb709fc0c4e0d78a8435d2749 Mon Sep 17 00:00:00 2001
From: Arvind Tiwari <tiwariav@gmail.com>
Date: Fri, 20 Apr 2018 20:27:26 +0530
Subject: [PATCH 09/20] refactor imports

---
 datacenterlight/views.py | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/datacenterlight/views.py b/datacenterlight/views.py
index 203846fb..49e4d15a 100644
--- a/datacenterlight/views.py
+++ b/datacenterlight/views.py
@@ -1,4 +1,3 @@
-import json
 import logging
 
 from django import forms
@@ -7,22 +6,18 @@ from django.contrib import messages
 from django.contrib.auth import login, authenticate
 from django.core.exceptions import ValidationError
 from django.core.urlresolvers import reverse
-from django.http import HttpResponseRedirect, HttpResponse, JsonResponse
+from django.http import HttpResponseRedirect, JsonResponse
 from django.shortcuts import render
 from django.utils.translation import get_language, ugettext_lazy as _
 from django.views.decorators.cache import cache_control
 from django.views.generic import FormView, CreateView, DetailView
 
-from datacenterlight.tasks import create_vm_task
 from hosting.forms import HostingUserLoginForm
-from hosting.models import HostingOrder, HostingBill
+from hosting.models import HostingOrder
 from membership.models import CustomUser, StripeCustomer
 from opennebula_api.serializers import VMTemplateSerializer
-from utils.forms import (
-    BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm
-)
+from utils.forms import BillingAddressForm, BillingAddressFormSignup
 from utils.hosting_utils import get_vm_price
-from utils.models import BillingAddress
 from utils.stripe_utils import StripeUtils
 from utils.tasks import send_plain_email_task
 from .forms import ContactForm

From 3b6c2b9d4e578b60c8e9264ce591034d3737569c Mon Sep 17 00:00:00 2001
From: Arvind Tiwari <tiwariav@gmail.com>
Date: Sat, 21 Apr 2018 22:27:43 +0530
Subject: [PATCH 10/20] fix vm_id default

---
 hosting/models.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/models.py b/hosting/models.py
index 09c6eb2a..de4d3aec 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -72,7 +72,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
         return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
 
     @classmethod
-    def create(cls, price=None, vm_id=None, customer=None,
+    def create(cls, price=None, vm_id=0, customer=None,
                billing_address=None, vm_pricing=None):
         instance = cls.objects.create(
             price=price,

From 88e6d9d21619e132eaa29587263fabfbad2e448a Mon Sep 17 00:00:00 2001
From: "M.Ravi" <mondi.ravi@gmail.com>
Date: Wed, 27 Jun 2018 12:24:16 +0200
Subject: [PATCH 11/20] Separate two error messages with a period

---
 datacenterlight/tasks.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py
index 66a91917..3554580f 100644
--- a/datacenterlight/tasks.py
+++ b/datacenterlight/tasks.py
@@ -121,7 +121,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
             )
             logger.error(emsg)
             if error_msg:
-                error_msg += emsg
+                error_msg += ". " + emsg
             else:
                 error_msg = emsg
 

From f1e021e1e9d9ded16fd32fffed5ad0c983b14ac8 Mon Sep 17 00:00:00 2001
From: "M.Ravi" <mondi.ravi@gmail.com>
Date: Wed, 27 Jun 2018 12:24:53 +0200
Subject: [PATCH 12/20] Improve comments

---
 datacenterlight/utils.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py
index 042eec73..8fed5dcd 100644
--- a/datacenterlight/utils.py
+++ b/datacenterlight/utils.py
@@ -67,12 +67,12 @@ def create_vm(billing_address_data, stripe_customer_id, specs,
         billing_address_user_form.is_valid()
         billing_address_user_form.save()
 
-    # Associate an order with a stripe subscription
+    # Associate the given stripe subscription with the order
     order.set_subscription_id(
         stripe_subscription_obj.id, card_details_dict
     )
 
-    # If the Stripe payment succeeds, set order status approved
+    # Set order status approved
     order.set_approved()
 
     create_vm_task.delay(vm_template_id, user, specs, template, order.id)

From 5eff54cffecd5616be658d4719829ac7b3aac4d5 Mon Sep 17 00:00:00 2001
From: "M.Ravi" <mondi.ravi@gmail.com>
Date: Wed, 27 Jun 2018 12:34:41 +0200
Subject: [PATCH 13/20] Fix a bug -- json not imported; use JsonResponse
 instead of HttpResponse

---
 hosting/views.py | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/hosting/views.py b/hosting/views.py
index 07bafd97..e5383535 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1266,10 +1266,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
                 ["%s=%s" % (k, v) for (k, v) in admin_email_body.items()]),
         }
         send_plain_email_task.delay(email_to_admin_data)
-        return HttpResponse(
-            json.dumps(response),
-            content_type="application/json"
-        )
+        return JsonResponse(response)
 
 
 class HostingBillListView(PermissionRequiredMixin, LoginRequiredMixin,

From 43b3a63958fcdec044cb3f2d736168bce6aeb0ae Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sun, 1 Jul 2018 16:25:02 +0200
Subject: [PATCH 14/20] Reorganize imports

---
 datacenterlight/tasks.py | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py
index 259a2e36..281d5f45 100644
--- a/datacenterlight/tasks.py
+++ b/datacenterlight/tasks.py
@@ -10,14 +10,12 @@ from django.utils import translation
 from django.utils.translation import ugettext_lazy as _
 
 from dynamicweb.celery import app
-from hosting.models import HostingOrder, HostingBill
-from membership.models import StripeCustomer, CustomUser
+from hosting.models import HostingOrder
+from membership.models import CustomUser
 from opennebula_api.models import OpenNebulaManager
 from opennebula_api.serializers import VirtualMachineSerializer
-from utils.forms import UserBillingAddressForm
 from utils.hosting_utils import get_all_public_keys, get_or_create_vm_detail
 from utils.mailer import BaseEmail
-from utils.models import BillingAddress
 from utils.stripe_utils import StripeUtils
 from .models import VMPricing
 

From 5851277d9aa0e9b6a234be89a53c075f4e066709 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sun, 1 Jul 2018 16:36:36 +0200
Subject: [PATCH 15/20] Reorganize imports

---
 datacenterlight/utils.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py
index 8fed5dcd..5388b9d3 100644
--- a/datacenterlight/utils.py
+++ b/datacenterlight/utils.py
@@ -5,8 +5,8 @@ from hosting.models import HostingOrder, HostingBill
 from membership.models import StripeCustomer
 from utils.forms import UserBillingAddressForm
 from utils.models import BillingAddress
-from .models import VMPricing
 from .cms_models import CMSIntegration
+from .models import VMPricing
 
 
 def get_cms_integration(name):

From 900f014d922fd73fb97da08a35e3074d1d2d74f4 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sun, 1 Jul 2018 18:33:10 +0200
Subject: [PATCH 16/20] Save order specifications in HostingOrder also

---
 datacenterlight/utils.py                      | 11 ++++--
 hosting/migrations/0045_auto_20180701_1631.py | 35 +++++++++++++++++++
 hosting/models.py                             | 16 ++++++++-
 3 files changed, 59 insertions(+), 3 deletions(-)
 create mode 100644 hosting/migrations/0045_auto_20180701_1631.py

diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py
index 5388b9d3..7b3ef73d 100644
--- a/datacenterlight/utils.py
+++ b/datacenterlight/utils.py
@@ -1,12 +1,12 @@
 from django.contrib.sites.models import Site
 
 from datacenterlight.tasks import create_vm_task
-from hosting.models import HostingOrder, HostingBill
+from hosting.models import HostingOrder, HostingBill, OrderSpecifications
 from membership.models import StripeCustomer
 from utils.forms import UserBillingAddressForm
 from utils.models import BillingAddress
 from .cms_models import CMSIntegration
-from .models import VMPricing
+from .models import VMPricing, VMTemplate
 
 
 def get_cms_integration(name):
@@ -53,6 +53,13 @@ def create_vm(billing_address_data, stripe_customer_id, specs,
         vm_pricing=vm_pricing
     )
 
+    order_specs_obj, obj_created = OrderSpecifications.objects.get_or_create(
+        vm_template=VMTemplate.objects.get(vm_template_id),
+        cores=specs['cpu'], memory=specs['memory'], ssd_size=specs['disk_size']
+    )
+    order.order_specs = order_specs_obj
+    order.save()
+
     # Create a Hosting Bill
     HostingBill.create(customer=customer, billing_address=billing_address)
 
diff --git a/hosting/migrations/0045_auto_20180701_1631.py b/hosting/migrations/0045_auto_20180701_1631.py
new file mode 100644
index 00000000..8af4d821
--- /dev/null
+++ b/hosting/migrations/0045_auto_20180701_1631.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-07-01 16:31
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import utils.mixins
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('datacenterlight', '0024_dclcalculatorpluginmodel_vm_templates_to_show'),
+        ('hosting', '0044_hostingorder_vm_pricing'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='OrderSpecifications',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('cores', models.IntegerField(default=0)),
+                ('memory', models.IntegerField(default=0)),
+                ('hdd_size', models.IntegerField(default=0)),
+                ('ssd_size', models.IntegerField(default=0)),
+                ('vm_template', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='datacenterlight.VMTemplate')),
+            ],
+            bases=(utils.mixins.AssignPermissionsMixin, models.Model),
+        ),
+        migrations.AddField(
+            model_name='hostingorder',
+            name='order_specs',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='hosting.OrderSpecifications'),
+        ),
+    ]
diff --git a/hosting/models.py b/hosting/models.py
index de4d3aec..c30a25a8 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -7,7 +7,7 @@ from django.utils import timezone
 from django.utils.functional import cached_property
 from Crypto.PublicKey import RSA
 
-from datacenterlight.models import VMPricing
+from datacenterlight.models import VMPricing, VMTemplate
 from membership.models import StripeCustomer, CustomUser
 from utils.models import BillingAddress
 from utils.mixins import AssignPermissionsMixin
@@ -41,6 +41,19 @@ class HostingPlan(models.Model):
         return price
 
 
+class OrderSpecifications(AssignPermissionsMixin, models.Model):
+    vm_template = models.ForeignKey(VMTemplate, blank=True, null=True)
+    cores = models.IntegerField(default=0)
+    memory = models.IntegerField(default=0)
+    hdd_size = models.IntegerField(default=0)
+    ssd_size = models.IntegerField(default=0)
+
+    def __str__(self):
+        return "%s - %s cores, %s GB RAM, %s GB SSD" % (
+            self.vm_template.name, self.cores, self.memory, self.ssd_size
+        )
+
+
 class HostingOrder(AssignPermissionsMixin, models.Model):
     ORDER_APPROVED_STATUS = 'Approved'
     ORDER_DECLINED_STATUS = 'Declined'
@@ -56,6 +69,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
     price = models.FloatField()
     subscription_id = models.CharField(max_length=100, null=True)
     vm_pricing = models.ForeignKey(VMPricing)
+    order_specs = models.ForeignKey(OrderSpecifications, null=True, blank=True)
 
     permissions = ('view_hostingorder',)
 

From f48005166e9d78b4b25dd02bb4225f8893fc31ac Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sun, 1 Jul 2018 18:42:11 +0200
Subject: [PATCH 17/20] Fix bug getting VMTemplate object from vm_template_id

---
 datacenterlight/utils.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py
index 7b3ef73d..3d0e4370 100644
--- a/datacenterlight/utils.py
+++ b/datacenterlight/utils.py
@@ -54,7 +54,9 @@ def create_vm(billing_address_data, stripe_customer_id, specs,
     )
 
     order_specs_obj, obj_created = OrderSpecifications.objects.get_or_create(
-        vm_template=VMTemplate.objects.get(vm_template_id),
+        vm_template=VMTemplate.objects.get(
+            opennebula_vm_template_id=vm_template_id
+        ),
         cores=specs['cpu'], memory=specs['memory'], ssd_size=specs['disk_size']
     )
     order.order_specs = order_specs_obj

From 7f57ace92d7f46bd0ae84029cbfcdade2726c784 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sun, 1 Jul 2018 19:23:05 +0200
Subject: [PATCH 18/20] Set default and on_delete attributes

---
 ...uto_20180701_1631.py => 0045_auto_20180701_1718.py} |  6 +++---
 hosting/models.py                                      | 10 ++++++++--
 2 files changed, 11 insertions(+), 5 deletions(-)
 rename hosting/migrations/{0045_auto_20180701_1631.py => 0045_auto_20180701_1718.py} (73%)

diff --git a/hosting/migrations/0045_auto_20180701_1631.py b/hosting/migrations/0045_auto_20180701_1718.py
similarity index 73%
rename from hosting/migrations/0045_auto_20180701_1631.py
rename to hosting/migrations/0045_auto_20180701_1718.py
index 8af4d821..d9705a24 100644
--- a/hosting/migrations/0045_auto_20180701_1631.py
+++ b/hosting/migrations/0045_auto_20180701_1718.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.9.4 on 2018-07-01 16:31
+# Generated by Django 1.9.4 on 2018-07-01 17:18
 from __future__ import unicode_literals
 
 from django.db import migrations, models
@@ -23,13 +23,13 @@ class Migration(migrations.Migration):
                 ('memory', models.IntegerField(default=0)),
                 ('hdd_size', models.IntegerField(default=0)),
                 ('ssd_size', models.IntegerField(default=0)),
-                ('vm_template', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='datacenterlight.VMTemplate')),
+                ('vm_template', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='datacenterlight.VMTemplate')),
             ],
             bases=(utils.mixins.AssignPermissionsMixin, models.Model),
         ),
         migrations.AddField(
             model_name='hostingorder',
             name='order_specs',
-            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='hosting.OrderSpecifications'),
+            field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.OrderSpecifications'),
         ),
     ]
diff --git a/hosting/models.py b/hosting/models.py
index c30a25a8..b182bfc5 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -42,7 +42,10 @@ class HostingPlan(models.Model):
 
 
 class OrderSpecifications(AssignPermissionsMixin, models.Model):
-    vm_template = models.ForeignKey(VMTemplate, blank=True, null=True)
+    vm_template = models.ForeignKey(
+        VMTemplate, blank=True, null=True, default=None,
+        on_delete=models.SET_NULL
+    )
     cores = models.IntegerField(default=0)
     memory = models.IntegerField(default=0)
     hdd_size = models.IntegerField(default=0)
@@ -69,7 +72,10 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
     price = models.FloatField()
     subscription_id = models.CharField(max_length=100, null=True)
     vm_pricing = models.ForeignKey(VMPricing)
-    order_specs = models.ForeignKey(OrderSpecifications, null=True, blank=True)
+    order_specs = models.ForeignKey(
+        OrderSpecifications, null=True, blank=True, default=None,
+        on_delete=models.SET_NULL
+    )
 
     permissions = ('view_hostingorder',)
 

From 00cb1de75d8998675d1e925df70806dad44ae939 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sun, 1 Jul 2018 19:46:21 +0200
Subject: [PATCH 19/20] Add type to OrderSpecification string

---
 hosting/models.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/hosting/models.py b/hosting/models.py
index b182bfc5..2ce604d0 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -52,8 +52,9 @@ class OrderSpecifications(AssignPermissionsMixin, models.Model):
     ssd_size = models.IntegerField(default=0)
 
     def __str__(self):
-        return "%s - %s cores, %s GB RAM, %s GB SSD" % (
-            self.vm_template.name, self.cores, self.memory, self.ssd_size
+        return "%s - %s, %s cores, %s GB RAM, %s GB SSD" % (
+            self.vm_template.name, self.vm_template.vm_type, self.cores,
+            self.memory, self.ssd_size
         )
 
 

From 44900f6a4886f4ded6b744bcebe3f4991018dad7 Mon Sep 17 00:00:00 2001
From: PCoder <purple.coder@yahoo.co.uk>
Date: Sun, 1 Jul 2018 22:30:23 +0200
Subject: [PATCH 20/20] Rename OrderSpecifications to OrderDetail

---
 datacenterlight/utils.py                                  | 6 +++---
 ...5_auto_20180701_1718.py => 0045_auto_20180701_2028.py} | 8 ++++----
 hosting/models.py                                         | 6 +++---
 3 files changed, 10 insertions(+), 10 deletions(-)
 rename hosting/migrations/{0045_auto_20180701_1718.py => 0045_auto_20180701_2028.py} (89%)

diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py
index 3d0e4370..a6f760af 100644
--- a/datacenterlight/utils.py
+++ b/datacenterlight/utils.py
@@ -1,7 +1,7 @@
 from django.contrib.sites.models import Site
 
 from datacenterlight.tasks import create_vm_task
-from hosting.models import HostingOrder, HostingBill, OrderSpecifications
+from hosting.models import HostingOrder, HostingBill, OrderDetail
 from membership.models import StripeCustomer
 from utils.forms import UserBillingAddressForm
 from utils.models import BillingAddress
@@ -53,13 +53,13 @@ def create_vm(billing_address_data, stripe_customer_id, specs,
         vm_pricing=vm_pricing
     )
 
-    order_specs_obj, obj_created = OrderSpecifications.objects.get_or_create(
+    order_detail_obj, obj_created = OrderDetail.objects.get_or_create(
         vm_template=VMTemplate.objects.get(
             opennebula_vm_template_id=vm_template_id
         ),
         cores=specs['cpu'], memory=specs['memory'], ssd_size=specs['disk_size']
     )
-    order.order_specs = order_specs_obj
+    order.order_detail = order_detail_obj
     order.save()
 
     # Create a Hosting Bill
diff --git a/hosting/migrations/0045_auto_20180701_1718.py b/hosting/migrations/0045_auto_20180701_2028.py
similarity index 89%
rename from hosting/migrations/0045_auto_20180701_1718.py
rename to hosting/migrations/0045_auto_20180701_2028.py
index d9705a24..39b58aa8 100644
--- a/hosting/migrations/0045_auto_20180701_1718.py
+++ b/hosting/migrations/0045_auto_20180701_2028.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.9.4 on 2018-07-01 17:18
+# Generated by Django 1.9.4 on 2018-07-01 20:28
 from __future__ import unicode_literals
 
 from django.db import migrations, models
@@ -16,7 +16,7 @@ class Migration(migrations.Migration):
 
     operations = [
         migrations.CreateModel(
-            name='OrderSpecifications',
+            name='OrderDetail',
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                 ('cores', models.IntegerField(default=0)),
@@ -29,7 +29,7 @@ class Migration(migrations.Migration):
         ),
         migrations.AddField(
             model_name='hostingorder',
-            name='order_specs',
-            field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.OrderSpecifications'),
+            name='order_detail',
+            field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.OrderDetail'),
         ),
     ]
diff --git a/hosting/models.py b/hosting/models.py
index 2ce604d0..411bd267 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -41,7 +41,7 @@ class HostingPlan(models.Model):
         return price
 
 
-class OrderSpecifications(AssignPermissionsMixin, models.Model):
+class OrderDetail(AssignPermissionsMixin, models.Model):
     vm_template = models.ForeignKey(
         VMTemplate, blank=True, null=True, default=None,
         on_delete=models.SET_NULL
@@ -73,8 +73,8 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
     price = models.FloatField()
     subscription_id = models.CharField(max_length=100, null=True)
     vm_pricing = models.ForeignKey(VMPricing)
-    order_specs = models.ForeignKey(
-        OrderSpecifications, null=True, blank=True, default=None,
+    order_detail = models.ForeignKey(
+        OrderDetail, null=True, blank=True, default=None,
         on_delete=models.SET_NULL
     )