From bd875ffe7deb21c8d99eaa0501a43ef75285141d Mon Sep 17 00:00:00 2001 From: PCoder 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 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 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 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 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 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" 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 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 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 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" 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" 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" 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 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 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 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 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 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 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 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 )