Merge pull request #489 from pcoder/task/3772/hosting_billing_monthly_subscription
Task/3772/hosting billing monthly subscription
This commit is contained in:
		
				commit
				
					
						2223bffa04
					
				
			
		
					 6 changed files with 450 additions and 217 deletions
				
			
		| 
						 | 
				
			
			@ -8,7 +8,7 @@ msgid ""
 | 
			
		|||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2017-09-03 16:44+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2017-09-16 14:09+0000\n"
 | 
			
		||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 | 
			
		||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
			
		||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
			
		||||
| 
						 | 
				
			
			@ -18,6 +18,10 @@ msgstr ""
 | 
			
		|||
"Content-Transfer-Encoding: 8bit\n"
 | 
			
		||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
 | 
			
		||||
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Your New VM %(vm_name)s at Data Center Light"
 | 
			
		||||
msgstr "Deine neue VM %(vm_name)s bei Data Center Light"
 | 
			
		||||
 | 
			
		||||
msgid "Enter name"
 | 
			
		||||
msgstr "Name"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -183,9 +187,18 @@ msgstr "Kontakt"
 | 
			
		|||
msgid "All Rights Reserved"
 | 
			
		||||
msgstr "Alle Rechte vorbehalten"
 | 
			
		||||
 | 
			
		||||
msgid "Toggle navigation"
 | 
			
		||||
msgstr "Konfiguration"
 | 
			
		||||
 | 
			
		||||
msgid "Why Data Center Light?"
 | 
			
		||||
msgstr "Warum Data Center Light?"
 | 
			
		||||
 | 
			
		||||
msgid "Login"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Dashboard"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Finally, an affordable VM hosting in Switzerland!"
 | 
			
		||||
msgstr "Endlich: bezahlbares VM Hosting in der Schweiz"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,16 +1,22 @@
 | 
			
		|||
from dynamicweb.celery import app
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
from celery.exceptions import MaxRetriesExceededError
 | 
			
		||||
from celery.utils.log import get_task_logger
 | 
			
		||||
from celery import current_task
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.core.mail import EmailMessage
 | 
			
		||||
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 opennebula_api.models import OpenNebulaManager
 | 
			
		||||
from opennebula_api.serializers import VirtualMachineSerializer
 | 
			
		||||
from hosting.models import HostingOrder, HostingBill
 | 
			
		||||
from utils.hosting_utils import get_all_public_keys
 | 
			
		||||
from utils.forms import UserBillingAddressForm
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from membership.models import StripeCustomer
 | 
			
		||||
from django.core.mail import EmailMessage
 | 
			
		||||
from utils.mailer import BaseEmail
 | 
			
		||||
from utils.models import BillingAddress
 | 
			
		||||
from celery.exceptions import MaxRetriesExceededError
 | 
			
		||||
 | 
			
		||||
logger = get_task_logger(__name__)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -53,19 +59,29 @@ def create_vm_task(self, vm_template_id, user, specs, template,
 | 
			
		|||
        billing_address = BillingAddress.objects.filter(
 | 
			
		||||
            id=billing_address_id).first()
 | 
			
		||||
        customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
 | 
			
		||||
        # Create OpenNebulaManager
 | 
			
		||||
        manager = OpenNebulaManager(email=settings.OPENNEBULA_USERNAME,
 | 
			
		||||
                                    password=settings.OPENNEBULA_PASSWORD)
 | 
			
		||||
 | 
			
		||||
        # Create a vm using oneadmin, also specify the name
 | 
			
		||||
        if 'pass' in user:
 | 
			
		||||
            on_user = user.get('email')
 | 
			
		||||
            on_pass = user.get('pass')
 | 
			
		||||
            logger.debug("Using user {user} to create VM".format(user=on_user))
 | 
			
		||||
            vm_name = None
 | 
			
		||||
        else:
 | 
			
		||||
            on_user = settings.OPENNEBULA_USERNAME
 | 
			
		||||
            on_pass = settings.OPENNEBULA_PASSWORD
 | 
			
		||||
            logger.debug("Using OpenNebula admin user to create VM")
 | 
			
		||||
            vm_name = "{email}-{template_name}-{date}".format(
 | 
			
		||||
                email=user.get('email'),
 | 
			
		||||
                template_name=template.get('name'),
 | 
			
		||||
                date=int(datetime.now().strftime("%s")))
 | 
			
		||||
 | 
			
		||||
        # Create OpenNebulaManager
 | 
			
		||||
        manager = OpenNebulaManager(email=on_user, password=on_pass)
 | 
			
		||||
 | 
			
		||||
        vm_id = manager.create_vm(
 | 
			
		||||
            template_id=vm_template_id,
 | 
			
		||||
            specs=specs,
 | 
			
		||||
            ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY,
 | 
			
		||||
            vm_name="{email}-{template_name}-{date}".format(
 | 
			
		||||
                email=user.get('email'),
 | 
			
		||||
                template_name=template.get('name'),
 | 
			
		||||
                date=int(datetime.now().strftime("%s")))
 | 
			
		||||
            vm_name=vm_name
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if vm_id is None:
 | 
			
		||||
| 
						 | 
				
			
			@ -124,6 +140,54 @@ def create_vm_task(self, vm_template_id, user, specs, template,
 | 
			
		|||
        }
 | 
			
		||||
        email = EmailMessage(**email_data)
 | 
			
		||||
        email.send()
 | 
			
		||||
 | 
			
		||||
        if 'pass' in user:
 | 
			
		||||
            lang = 'en-us' 
 | 
			
		||||
            if user.get('language') is not None:
 | 
			
		||||
                logger.debug("Language is set to {}".format(user.get('language')))
 | 
			
		||||
                lang = user.get('language')
 | 
			
		||||
            translation.activate(lang)
 | 
			
		||||
            # Send notification to the user as soon as VM has been booked
 | 
			
		||||
            context = {
 | 
			
		||||
                'vm': vm,
 | 
			
		||||
                'order': order,
 | 
			
		||||
                'base_url': "{0}://{1}".format(user.get('request_scheme'),
 | 
			
		||||
                                               user.get('request_host')),
 | 
			
		||||
                'page_header': _(
 | 
			
		||||
                    'Your New VM %(vm_name)s at Data Center Light') % {
 | 
			
		||||
                                   'vm_name': vm.get('name')}
 | 
			
		||||
            }
 | 
			
		||||
            email_data = {
 | 
			
		||||
                'subject': context.get('page_header'),
 | 
			
		||||
                'to': user.get('email'),
 | 
			
		||||
                'context': context,
 | 
			
		||||
                'template_name': 'new_booked_vm',
 | 
			
		||||
                'template_path': 'hosting/emails/',
 | 
			
		||||
                'from_address': settings.DCL_SUPPORT_FROM_ADDRESS,
 | 
			
		||||
            }
 | 
			
		||||
            email = BaseEmail(**email_data)
 | 
			
		||||
            email.send()
 | 
			
		||||
 | 
			
		||||
            # try to see if we have the IP and that if the ssh keys can
 | 
			
		||||
            # be configured
 | 
			
		||||
            new_host = manager.get_primary_ipv4(vm_id)
 | 
			
		||||
            logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
 | 
			
		||||
            if new_host is not None:
 | 
			
		||||
                custom_user = CustomUser.objects.get(email=user.get('email'))
 | 
			
		||||
                if custom_user is not None:
 | 
			
		||||
                    public_keys = get_all_public_keys(custom_user)
 | 
			
		||||
                    keys = [{'value': key, 'state': True} for key in
 | 
			
		||||
                            public_keys]
 | 
			
		||||
                    if len(keys) > 0:
 | 
			
		||||
                        logger.debug(
 | 
			
		||||
                            "Calling configure on {host} for {num_keys} keys".format(
 | 
			
		||||
                                host=new_host, num_keys=len(keys)))
 | 
			
		||||
                        # Let's delay the task by 75 seconds to be sure
 | 
			
		||||
                        # that we run the cdist configure after the host
 | 
			
		||||
                        # is up
 | 
			
		||||
                        manager.manage_public_key(keys,
 | 
			
		||||
                                                  hosts=[new_host],
 | 
			
		||||
                                                  countdown=75)
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        logger.error(str(e))
 | 
			
		||||
        try:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,37 @@
 | 
			
		|||
$(document).ready(function () {
 | 
			
		||||
    var create_vm_form = $('#virtual_machine_create_form');
 | 
			
		||||
    create_vm_form.submit(function () {
 | 
			
		||||
        $('#btn-create-vm').prop('disabled', true);
 | 
			
		||||
        $.ajax({
 | 
			
		||||
            url: create_vm_form.attr('action'),
 | 
			
		||||
            type: 'POST',
 | 
			
		||||
            data: create_vm_form.serialize(),
 | 
			
		||||
            success: function (data) {
 | 
			
		||||
                if (data.status === true) {
 | 
			
		||||
                    fa_icon = $('.modal-icon > .fa');
 | 
			
		||||
                    fa_icon.attr('class', 'fa fa-check');
 | 
			
		||||
                    $('.modal-header > .close').attr('class', 'close');
 | 
			
		||||
                    $('#createvm-modal-title').text(data.msg_title);
 | 
			
		||||
                    $('#createvm-modal-body').text(data.msg_body);
 | 
			
		||||
                    $('#createvm-modal').on('hidden.bs.modal', function () {
 | 
			
		||||
                        window.location = data.redirect;
 | 
			
		||||
                    })
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            error: function (xmlhttprequest, textstatus, message) {
 | 
			
		||||
                    fa_icon = $('.modal-icon > .fa');
 | 
			
		||||
                    fa_icon.attr('class', 'fa fa-times');
 | 
			
		||||
                    $('.modal-header > .close').attr('class', 'close');
 | 
			
		||||
                    if (typeof(create_vm_error_message) !== 'undefined') {
 | 
			
		||||
                        $('#createvm-modal-title').text(create_vm_error_message);
 | 
			
		||||
                    }
 | 
			
		||||
                    $('#btn-create-vm').prop('disabled', false);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        return false;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
$( document ).ready(function() {
 | 
			
		||||
 | 
			
		||||
	$('#confirm-cancel').on('click', '.btn-ok', function(e) {
 | 
			
		||||
    $('#confirm-cancel').on('click', '.btn-ok', function (e) {
 | 
			
		||||
        $('#virtual_machine_cancel_form').trigger('submit');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,8 @@
 | 
			
		|||
{% extends "hosting/base_short.html" %}
 | 
			
		||||
{% load staticfiles bootstrap3 %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% load custom_tags %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
 | 
			
		||||
<div class="order-detail-container">
 | 
			
		||||
| 
						 | 
				
			
			@ -20,30 +22,58 @@
 | 
			
		|||
    <div class="row">
 | 
			
		||||
        <div class="col-xs-12 col-md-8 col-md-offset-2">
 | 
			
		||||
            <div class="invoice-title">
 | 
			
		||||
    			<h2>{{page_header_text}}</h2><h3 class="pull-right">{% trans "Order #"%} {{order.id}}</h3>
 | 
			
		||||
                <h2>{{page_header_text}}</h2>
 | 
			
		||||
                <h3 class="pull-right">
 | 
			
		||||
                    {% if order %}
 | 
			
		||||
                    {% trans "Order #"%} {{order.id}}
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </h3>
 | 
			
		||||
            </div>
 | 
			
		||||
            <hr>
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-xs-12 col-md-6 pull-right order-confirm-date">
 | 
			
		||||
                    <address>
 | 
			
		||||
                        <strong>{% trans "Date"%}:</strong><br>
 | 
			
		||||
                        <span id="order-created_at">{{order.created_at|date:'Y-m-d H:i'}}</span><br><br>
 | 
			
		||||
                        <span id="order-created_at">
 | 
			
		||||
                                {% if order %}
 | 
			
		||||
                                    {{order.created_at|date:'Y-m-d H:i'}}
 | 
			
		||||
                                {% else %}
 | 
			
		||||
                                    {% now "Y-m-d H:i" %}
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </span><br><br>
 | 
			
		||||
                        {% if order %}
 | 
			
		||||
                        <strong>{% trans "Status:"%}</strong><br>
 | 
			
		||||
                        {% if order.status == 'Approved' %}
 | 
			
		||||
                            <strong class="text-success">{% trans "Approved" %}</strong>
 | 
			
		||||
                        <strong class="text-success">
 | 
			
		||||
                            {% trans "Approved" %}
 | 
			
		||||
                        </strong>
 | 
			
		||||
                        {% else %}
 | 
			
		||||
                            <strong class="text-danger">{% trans "Declined" %}</strong>
 | 
			
		||||
                        <strong class="text-danger">
 | 
			
		||||
                            {% trans "Declined" %}
 | 
			
		||||
                        </strong>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        <br><br>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    </address>
 | 
			
		||||
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-xs-12 col-md-6">
 | 
			
		||||
                    <address>
 | 
			
		||||
                        <h3><b>{% trans "Billed To:"%}</b></h3>
 | 
			
		||||
                        {% if order %}
 | 
			
		||||
                        {{user.name}}<br>
 | 
			
		||||
                        {{order.billing_address.street_address}},{{order.billing_address.postal_code}}<br>
 | 
			
		||||
                        {{order.billing_address.city}}, {{order.billing_address.country}}.
 | 
			
		||||
                        {{order.billing_address.city}},
 | 
			
		||||
                        {{order.billing_address.country}}.
 | 
			
		||||
                        {% else %}
 | 
			
		||||
                        {% with request.session.billing_address_data as billing_address %}
 | 
			
		||||
                        {{billing_address|get_value_from_dict:'cardholder_name'}}<br>
 | 
			
		||||
                        {{billing_address|get_value_from_dict:'street_address'}},
 | 
			
		||||
                        {{billing_address|get_value_from_dict:'postal_code'}}<br>
 | 
			
		||||
                        {{billing_address|get_value_from_dict:'city'}},
 | 
			
		||||
                        {{billing_address|get_value_from_dict:'country'}}.
 | 
			
		||||
                        {% endwith %}
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    </address>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -52,8 +82,15 @@
 | 
			
		|||
                <div class="col-xs-6">
 | 
			
		||||
                    <address>
 | 
			
		||||
                        <strong>{% trans "Payment Method:"%}</strong><br>
 | 
			
		||||
    					{{order.cc_brand}} {% trans "ending in" %} **** {{order.last4}}<br>
 | 
			
		||||
                        {% if order %}
 | 
			
		||||
                        {{order.cc_brand}} {% trans "ending in" %} ****
 | 
			
		||||
                        {{order.last4}}<br>
 | 
			
		||||
                        {{user.email}}
 | 
			
		||||
                        {% else %}
 | 
			
		||||
                        {{cc_brand}} {% trans "ending in" %} ****
 | 
			
		||||
                        {{cc_last4}}<br>
 | 
			
		||||
                        {{request.session.user.email}}
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    </address>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -65,31 +102,111 @@
 | 
			
		|||
            <h3><b>{% trans "Order summary"%}</b></h3>
 | 
			
		||||
            <hr>
 | 
			
		||||
            <div class="content">
 | 
			
		||||
                <p><b>{% trans "Cores"%}</b> <span class="pull-right">{{vm.cores}}</span></p>
 | 
			
		||||
                {% if request.session.specs %}
 | 
			
		||||
                {% with request.session.specs as vm %}
 | 
			
		||||
                <p><b>{% trans "Cores"%}</b>
 | 
			
		||||
                    <span class="pull-right">{{vm.cpu}}</span>
 | 
			
		||||
                </p>
 | 
			
		||||
                <hr>
 | 
			
		||||
                <p><b>{% trans "Memory"%}</b> <span class="pull-right">{{vm.memory}} GB</span></p>
 | 
			
		||||
                <p><b>{% trans "Memory"%}</b>
 | 
			
		||||
                    <span class="pull-right">{{vm.memory}} GB</span>
 | 
			
		||||
                </p>
 | 
			
		||||
                <hr>
 | 
			
		||||
                <p><b>{% trans "Disk space"%}</b> <span class="pull-right">{{vm.disk_size}} GB</span></p>
 | 
			
		||||
                <p><b>{% trans "Disk space"%}</b>
 | 
			
		||||
                    <span class="pull-right">{{vm.disk_size}} GB</span>
 | 
			
		||||
                </p>
 | 
			
		||||
                <hr>
 | 
			
		||||
                                <h4>{% trans "Total"%}<p class="pull-right"><b>{{vm.price}} CHF</b></p></h4>
 | 
			
		||||
                <p><b>{% trans "Configuration"%}</b>
 | 
			
		||||
                    <span class="pull-right">{{request.session.template.name}}</span>
 | 
			
		||||
                </p>
 | 
			
		||||
                <hr>
 | 
			
		||||
                <h4>{% trans "Total"%}
 | 
			
		||||
                    <p class="pull-right">
 | 
			
		||||
                        <b>{{vm.price}} CHF</b>
 | 
			
		||||
                        <span class="dcl-price-month"> /{% trans "Month" %}
 | 
			
		||||
                                </span>
 | 
			
		||||
                    </p>
 | 
			
		||||
                </h4>
 | 
			
		||||
                {% endwith %}
 | 
			
		||||
                {% else %}
 | 
			
		||||
                <p><b>{% trans "Cores"%}</b>
 | 
			
		||||
                    <span class="pull-right">{{vm.cores}}</span>
 | 
			
		||||
                </p>
 | 
			
		||||
                <hr>
 | 
			
		||||
                <p><b>{% trans "Memory"%}</b>
 | 
			
		||||
                    <span class="pull-right">{{vm.memory}} GB</span>
 | 
			
		||||
                </p>
 | 
			
		||||
                <hr>
 | 
			
		||||
                <p><b>{% trans "Disk space"%}</b>
 | 
			
		||||
                    <span class="pull-right">{{vm.disk_size}} GB</span>
 | 
			
		||||
                </p>
 | 
			
		||||
                <hr>
 | 
			
		||||
                <h4>{% trans "Total"%}<p class="pull-right"><b>{{vm.price}}
 | 
			
		||||
                    CHF</b><span
 | 
			
		||||
                        class="dcl-price-month"> /{% trans "Month" %}</span>
 | 
			
		||||
                </p></h4>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </div>
 | 
			
		||||
            <br/>
 | 
			
		||||
            {% url 'hosting:payment' as payment_url %}
 | 
			
		||||
            {% if payment_url in request.META.HTTP_REFERER  %}
 | 
			
		||||
            <div class=" content pull-right">
 | 
			
		||||
                <a href="{% url 'hosting:virtual_machines'%}" ><button class="btn btn-info">{% trans "Finish Configuration"%}</button></a>
 | 
			
		||||
            {% if not order %}
 | 
			
		||||
            <form method="post" id="virtual_machine_create_form">
 | 
			
		||||
                {% csrf_token %}
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col-sm-8">
 | 
			
		||||
                        <p class="dcl-place-order-text">{% blocktrans with vm_price=request.session.specs.price %}By clicking "Place order" this plan will charge your credit card account with the fee of {{ vm_price }}CHF/month{% endblocktrans %}.</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-sm-4 content">
 | 
			
		||||
                        <button class="btn btn-info pull-right"
 | 
			
		||||
                                id="btn-create-vm"
 | 
			
		||||
                                data-href="{% url 'hosting:order-confirmation' %}"
 | 
			
		||||
                                data-toggle="modal"
 | 
			
		||||
                                data-target="#createvm-modal">
 | 
			
		||||
                            {% trans "Place order"%}
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </form>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
</div>
 | 
			
		||||
<!-- Create VM Modal -->
 | 
			
		||||
<div class="modal fade" id="createvm-modal" tabindex="-1" role="dialog"
 | 
			
		||||
     aria-hidden="true" data-backdrop="static" data-keyboard="false">
 | 
			
		||||
    <div class="modal-dialog">
 | 
			
		||||
        <div class="modal-content">
 | 
			
		||||
            <div class="modal-header">
 | 
			
		||||
                <button type="button" class="close hidden" data-dismiss="modal"
 | 
			
		||||
                        aria-label="create-vm-close">
 | 
			
		||||
                    <span aria-hidden="true">×</span>
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="modal-body">
 | 
			
		||||
                <div class="modal-icon">
 | 
			
		||||
                    <i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
 | 
			
		||||
                    <span class="sr-only">{% trans "Processing..." %}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <h4 class="modal-title" id="createvm-modal-title">
 | 
			
		||||
                </h4>
 | 
			
		||||
                <div class="modal-text" id="createvm-modal-body">
 | 
			
		||||
                    {% trans "Hold tight, we are processing your request" %}
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="modal-footer">
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
<!-- / Create VM Modal -->
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript">
 | 
			
		||||
    {% trans "Some problem encountered. Please try again later." as err_msg %}
 | 
			
		||||
    var create_vm_error_message = '{{err_msg|safe}}.';
 | 
			
		||||
 | 
			
		||||
    window.onload = function () {
 | 
			
		||||
            var locale_date = moment.utc(document.getElementById("order-created_at").textContent,'YYYY-MM-DD HH:mm').toDate();
 | 
			
		||||
        var locale_date = moment.utc(document.getElementById("order-created_at").textContent, 'YYYY-MM-DD HH:mm').toDate();
 | 
			
		||||
        locale_date = moment(locale_date).format("YYYY-MM-DD h:mm:ss a");
 | 
			
		||||
        document.getElementById('order-created_at').innerHTML = locale_date;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,9 +21,13 @@ urlpatterns = [
 | 
			
		|||
    url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
 | 
			
		||||
    url(r'settings/?$', SettingsView.as_view(), name='settings'),
 | 
			
		||||
    url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
 | 
			
		||||
    url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'),
 | 
			
		||||
    url(r'order-confirmation/?$', OrdersHostingDetailView.as_view(),
 | 
			
		||||
        name='order-confirmation'),
 | 
			
		||||
    url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(),
 | 
			
		||||
        name='orders'),
 | 
			
		||||
    url(r'bills/?$', HostingBillListView.as_view(), name='bills'),
 | 
			
		||||
    url(r'bills/(?P<pk>\d+)/?$', HostingBillDetailView.as_view(), name='bills'),
 | 
			
		||||
    url(r'bills/(?P<pk>\d+)/?$', HostingBillDetailView.as_view(),
 | 
			
		||||
        name='bills'),
 | 
			
		||||
    url(r'cancel_order/(?P<pk>\d+)/?$',
 | 
			
		||||
        OrdersHostingDeleteView.as_view(), name='delete_order'),
 | 
			
		||||
    url(r'create_virtual_machine/?$', CreateVirtualMachinesView.as_view(),
 | 
			
		||||
| 
						 | 
				
			
			@ -40,13 +44,16 @@ urlpatterns = [
 | 
			
		|||
        name='delete_ssh_key'),
 | 
			
		||||
    url(r'create_ssh_key/?$', SSHKeyCreateView.as_view(),
 | 
			
		||||
        name='create_ssh_key'),
 | 
			
		||||
    url(r'^notifications/$', NotificationsView.as_view(), name='notifications'),
 | 
			
		||||
    url(r'^notifications/$', NotificationsView.as_view(),
 | 
			
		||||
        name='notifications'),
 | 
			
		||||
    url(r'^notifications/(?P<pk>\d+)/?$', MarkAsReadNotificationView.as_view(),
 | 
			
		||||
        name='read_notification'),
 | 
			
		||||
    url(r'login/?$', LoginView.as_view(), name='login'),
 | 
			
		||||
    url(r'signup/?$', SignupView.as_view(), name='signup'),
 | 
			
		||||
    url(r'signup-validate/?$', SignupValidateView.as_view(), name='signup-validate'),
 | 
			
		||||
    url(r'reset-password/?$', PasswordResetView.as_view(), name='reset_password'),
 | 
			
		||||
    url(r'signup-validate/?$', SignupValidateView.as_view(),
 | 
			
		||||
        name='signup-validate'),
 | 
			
		||||
    url(r'reset-password/?$', PasswordResetView.as_view(),
 | 
			
		||||
        name='reset_password'),
 | 
			
		||||
    url(r'reset-password-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',
 | 
			
		||||
        PasswordResetConfirmView.as_view(), name='reset_password_confirm'),
 | 
			
		||||
    url(r'^logout/?$', auth_views.logout,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										250
									
								
								hosting/views.py
									
										
									
									
									
								
							
							
						
						
									
										250
									
								
								hosting/views.py
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
import json
 | 
			
		||||
import logging
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -8,12 +9,12 @@ from django.contrib.auth.tokens import default_token_generator
 | 
			
		|||
from django.core.files.base import ContentFile
 | 
			
		||||
from django.core.urlresolvers import reverse_lazy, reverse
 | 
			
		||||
from django.http import Http404
 | 
			
		||||
from django.http import HttpResponseRedirect
 | 
			
		||||
from django.http import HttpResponseRedirect, HttpResponse
 | 
			
		||||
from django.shortcuts import redirect
 | 
			
		||||
from django.shortcuts import render
 | 
			
		||||
from django.utils.http import urlsafe_base64_decode
 | 
			
		||||
from django.utils.safestring import mark_safe
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
from django.utils.translation import get_language, ugettext_lazy as _
 | 
			
		||||
from django.views.generic import View, CreateView, FormView, ListView, \
 | 
			
		||||
    DetailView, \
 | 
			
		||||
    DeleteView, TemplateView, UpdateView
 | 
			
		||||
| 
						 | 
				
			
			@ -23,13 +24,13 @@ from stored_messages.api import mark_read
 | 
			
		|||
from stored_messages.models import Message
 | 
			
		||||
from stored_messages.settings import stored_messages_settings
 | 
			
		||||
 | 
			
		||||
from datacenterlight.tasks import create_vm_task
 | 
			
		||||
from membership.models import CustomUser, StripeCustomer
 | 
			
		||||
from opennebula_api.models import OpenNebulaManager
 | 
			
		||||
from opennebula_api.serializers import VirtualMachineSerializer, \
 | 
			
		||||
    VirtualMachineTemplateSerializer
 | 
			
		||||
from utils.forms import BillingAddressForm, PasswordResetRequestForm, \
 | 
			
		||||
    UserBillingAddressForm
 | 
			
		||||
from utils.hosting_utils import get_all_public_keys
 | 
			
		||||
from utils.mailer import BaseEmail
 | 
			
		||||
from utils.stripe_utils import StripeUtils
 | 
			
		||||
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, \
 | 
			
		||||
| 
						 | 
				
			
			@ -606,23 +607,10 @@ class PaymentVMView(LoginRequiredMixin, FormView):
 | 
			
		|||
    def post(self, request, *args, **kwargs):
 | 
			
		||||
        form = self.get_form()
 | 
			
		||||
        if form.is_valid():
 | 
			
		||||
 | 
			
		||||
            # Get billing address data
 | 
			
		||||
            billing_address_data = form.cleaned_data
 | 
			
		||||
 | 
			
		||||
            context = self.get_context_data()
 | 
			
		||||
 | 
			
		||||
            template = request.session.get('template')
 | 
			
		||||
            specs = request.session.get('specs')
 | 
			
		||||
 | 
			
		||||
            vm_template_id = template.get('id', 1)
 | 
			
		||||
 | 
			
		||||
            final_price = specs.get('price')
 | 
			
		||||
 | 
			
		||||
            token = form.cleaned_data.get('token')
 | 
			
		||||
 | 
			
		||||
            owner = self.request.user
 | 
			
		||||
 | 
			
		||||
            # Get or create stripe customer
 | 
			
		||||
            customer = StripeCustomer.get_or_create(email=owner.email,
 | 
			
		||||
                                                    token=token)
 | 
			
		||||
| 
						 | 
				
			
			@ -636,115 +624,18 @@ class PaymentVMView(LoginRequiredMixin, FormView):
 | 
			
		|||
 | 
			
		||||
            # Create Billing Address
 | 
			
		||||
            billing_address = form.save()
 | 
			
		||||
 | 
			
		||||
            # Make stripe charge to a customer
 | 
			
		||||
            stripe_utils = StripeUtils()
 | 
			
		||||
            charge_response = stripe_utils.make_charge(amount=final_price,
 | 
			
		||||
                                                       customer=customer.stripe_id)
 | 
			
		||||
 | 
			
		||||
            # Check if the payment was approved
 | 
			
		||||
            if not charge_response.get('response_object'):
 | 
			
		||||
                msg = charge_response.get('error')
 | 
			
		||||
                messages.add_message(
 | 
			
		||||
                    self.request, messages.ERROR, msg,
 | 
			
		||||
                    extra_tags='make_charge_error')
 | 
			
		||||
                return HttpResponseRedirect(
 | 
			
		||||
                    reverse('hosting:payment') + '#payment_error')
 | 
			
		||||
 | 
			
		||||
            charge = charge_response.get('response_object')
 | 
			
		||||
 | 
			
		||||
            # Create OpenNebulaManager
 | 
			
		||||
            manager = OpenNebulaManager(email=owner.email,
 | 
			
		||||
                                        password=owner.password)
 | 
			
		||||
            # Get user ssh key
 | 
			
		||||
            if not UserHostingKey.objects.filter(
 | 
			
		||||
                    user=self.request.user).exists():
 | 
			
		||||
                context.update({
 | 
			
		||||
                    'sshError': 'error',
 | 
			
		||||
                    'form': form
 | 
			
		||||
                })
 | 
			
		||||
                return render(request, self.template_name, context)
 | 
			
		||||
 | 
			
		||||
            # Create a vm using logged user
 | 
			
		||||
            vm_id = manager.create_vm(
 | 
			
		||||
                template_id=vm_template_id,
 | 
			
		||||
                specs=specs,
 | 
			
		||||
                ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            # 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 payment
 | 
			
		||||
            order.set_stripe_charge(charge)
 | 
			
		||||
 | 
			
		||||
            # If the Stripe payment was successed, set order status approved
 | 
			
		||||
            order.set_approved()
 | 
			
		||||
 | 
			
		||||
            vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
 | 
			
		||||
 | 
			
		||||
            # Send notification to the user as soon as VM has been booked
 | 
			
		||||
            context = {
 | 
			
		||||
                'vm': vm,
 | 
			
		||||
                'order': order,
 | 
			
		||||
                'base_url': "{0}://{1}".format(request.scheme,
 | 
			
		||||
                                               request.get_host()),
 | 
			
		||||
                'page_header': _(
 | 
			
		||||
                    'Your New VM %(vm_name)s at Data Center Light') % {
 | 
			
		||||
                                   'vm_name': vm.get('name')}
 | 
			
		||||
            }
 | 
			
		||||
            email_data = {
 | 
			
		||||
                'subject': context.get('page_header'),
 | 
			
		||||
                'to': request.user.email,
 | 
			
		||||
                'context': context,
 | 
			
		||||
                'template_name': 'new_booked_vm',
 | 
			
		||||
                'template_path': 'hosting/emails/',
 | 
			
		||||
                'from_address': settings.DCL_SUPPORT_FROM_ADDRESS,
 | 
			
		||||
            }
 | 
			
		||||
            email = BaseEmail(**email_data)
 | 
			
		||||
            email.send()
 | 
			
		||||
 | 
			
		||||
            # try to see if we have the IP and that if the ssh keys can
 | 
			
		||||
            # be configured
 | 
			
		||||
            new_host = manager.get_primary_ipv4(vm_id)
 | 
			
		||||
            if new_host is not None:
 | 
			
		||||
                public_keys = get_all_public_keys(owner)
 | 
			
		||||
                keys = [{'value': key, 'state': True} for key in public_keys]
 | 
			
		||||
                logger.debug(
 | 
			
		||||
                    "Calling configure on {host} for {num_keys} keys".format(
 | 
			
		||||
                        host=new_host, num_keys=len(keys)))
 | 
			
		||||
                # Let's delay the task by 75 seconds to be sure that we run
 | 
			
		||||
                # the cdist configure after the host is up
 | 
			
		||||
                manager.manage_public_key(keys, hosts=[new_host], countdown=75)
 | 
			
		||||
 | 
			
		||||
            return HttpResponseRedirect(
 | 
			
		||||
                "{url}?{query_params}".format(
 | 
			
		||||
                    url=reverse('hosting:orders', kwargs={'pk': order.id}),
 | 
			
		||||
            request.session['billing_address_data'] = billing_address_data
 | 
			
		||||
            request.session['billing_address'] = billing_address.id
 | 
			
		||||
            request.session['token'] = token
 | 
			
		||||
            request.session['customer'] = customer.id
 | 
			
		||||
            return HttpResponseRedirect("{url}?{query_params}".format(
 | 
			
		||||
                url=reverse('hosting:order-confirmation'),
 | 
			
		||||
                query_params='page=payment'))
 | 
			
		||||
        else:
 | 
			
		||||
            return self.form_invalid(form)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin,
 | 
			
		||||
class OrdersHostingDetailView(LoginRequiredMixin,
 | 
			
		||||
                              DetailView):
 | 
			
		||||
    template_name = "hosting/order_detail.html"
 | 
			
		||||
    context_object_name = "order"
 | 
			
		||||
| 
						 | 
				
			
			@ -752,18 +643,42 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin,
 | 
			
		|||
    permission_required = ['view_hostingorder']
 | 
			
		||||
    model = HostingOrder
 | 
			
		||||
 | 
			
		||||
    def get_object(self):
 | 
			
		||||
        return HostingOrder.objects.filter(
 | 
			
		||||
            pk=self.kwargs.get('pk')) if self.kwargs.get('pk') else None
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        # Get context
 | 
			
		||||
        context = super(DetailView, self).get_context_data(**kwargs)
 | 
			
		||||
        obj = self.get_object()
 | 
			
		||||
        owner = self.request.user
 | 
			
		||||
        manager = OpenNebulaManager(email=owner.email,
 | 
			
		||||
                                    password=owner.password)
 | 
			
		||||
        if 'specs' not in self.request.session:
 | 
			
		||||
            return HttpResponseRedirect(
 | 
			
		||||
                reverse('hosting:create_virtual_machine'))
 | 
			
		||||
        if 'token' not in self.request.session:
 | 
			
		||||
            return HttpResponseRedirect(reverse('hosting:payment'))
 | 
			
		||||
        stripe_customer_id = self.request.session.get('customer')
 | 
			
		||||
        customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
 | 
			
		||||
        stripe_utils = StripeUtils()
 | 
			
		||||
        card_details = stripe_utils.get_card_details(customer.stripe_id,
 | 
			
		||||
                                                     self.request.session.get(
 | 
			
		||||
                                                         'token'))
 | 
			
		||||
        if not card_details.get('response_object'):
 | 
			
		||||
            msg = card_details.get('error')
 | 
			
		||||
            messages.add_message(self.request, messages.ERROR, msg,
 | 
			
		||||
                                 extra_tags='failed_payment')
 | 
			
		||||
            return HttpResponseRedirect(
 | 
			
		||||
                reverse('hosting:payment') + '#payment_error')
 | 
			
		||||
 | 
			
		||||
        if self.request.GET.get('page', '') == 'payment':
 | 
			
		||||
            context['page_header_text'] = _('Confirm Order')
 | 
			
		||||
        else:
 | 
			
		||||
            context['page_header_text'] = _('Invoice')
 | 
			
		||||
 | 
			
		||||
        if obj is not None:
 | 
			
		||||
            try:
 | 
			
		||||
                manager = OpenNebulaManager(email=owner.email,
 | 
			
		||||
                                            password=owner.password)
 | 
			
		||||
                vm = manager.get_vm(obj.vm_id)
 | 
			
		||||
                context['vm'] = VirtualMachineSerializer(vm).data
 | 
			
		||||
            except WrongIdError:
 | 
			
		||||
| 
						 | 
				
			
			@ -775,11 +690,98 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin,
 | 
			
		|||
                context['error'] = 'WrongIdError'
 | 
			
		||||
            except ConnectionRefusedError:
 | 
			
		||||
                messages.error(self.request,
 | 
			
		||||
                           _(
 | 
			
		||||
                               'In order to create a VM, you need to create/upload your SSH KEY first.')
 | 
			
		||||
                               'In order to create a VM, you need to create/upload your SSH KEY first.'
 | 
			
		||||
                               )
 | 
			
		||||
        else:
 | 
			
		||||
            context['site_url'] = reverse('hosting:create_virtual_machine')
 | 
			
		||||
            context['cc_last4'] = card_details.get('response_object').get(
 | 
			
		||||
                'last4')
 | 
			
		||||
            context['cc_brand'] = card_details.get('response_object').get(
 | 
			
		||||
                'cc_brand')
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
    def post(self, request):
 | 
			
		||||
        template = request.session.get('template')
 | 
			
		||||
        specs = request.session.get('specs')
 | 
			
		||||
        stripe_customer_id = request.session.get('customer')
 | 
			
		||||
        customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
 | 
			
		||||
        billing_address_data = request.session.get('billing_address_data')
 | 
			
		||||
        billing_address_id = request.session.get('billing_address')
 | 
			
		||||
        vm_template_id = template.get('id', 1)
 | 
			
		||||
 | 
			
		||||
        # Make stripe charge to a customer
 | 
			
		||||
        stripe_utils = StripeUtils()
 | 
			
		||||
        card_details = stripe_utils.get_card_details(customer.stripe_id,
 | 
			
		||||
                                                     request.session.get(
 | 
			
		||||
                                                         'token'))
 | 
			
		||||
        if not card_details.get('response_object'):
 | 
			
		||||
            msg = card_details.get('error')
 | 
			
		||||
            messages.add_message(self.request, messages.ERROR, msg,
 | 
			
		||||
                                 extra_tags='failed_payment')
 | 
			
		||||
            return HttpResponseRedirect(
 | 
			
		||||
                reverse('datacenterlight:payment') + '#payment_error')
 | 
			
		||||
        card_details_dict = card_details.get('response_object')
 | 
			
		||||
        cpu = specs.get('cpu')
 | 
			
		||||
        memory = specs.get('memory')
 | 
			
		||||
        disk_size = specs.get('disk_size')
 | 
			
		||||
        amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6)
 | 
			
		||||
        plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format(
 | 
			
		||||
            cpu=cpu,
 | 
			
		||||
            memory=memory,
 | 
			
		||||
            disk_size=disk_size)
 | 
			
		||||
 | 
			
		||||
        stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
 | 
			
		||||
                                                        ram=memory,
 | 
			
		||||
                                                        ssd=disk_size,
 | 
			
		||||
                                                        version=1,
 | 
			
		||||
                                                        app='dcl')
 | 
			
		||||
        stripe_plan = stripe_utils.get_or_create_stripe_plan(
 | 
			
		||||
            amount=amount_to_be_charged,
 | 
			
		||||
            name=plan_name,
 | 
			
		||||
            stripe_plan_id=stripe_plan_id)
 | 
			
		||||
        subscription_result = stripe_utils.subscribe_customer_to_plan(
 | 
			
		||||
            customer.stripe_id,
 | 
			
		||||
            [{"plan": stripe_plan.get(
 | 
			
		||||
                'response_object').stripe_plan_id}])
 | 
			
		||||
        stripe_subscription_obj = subscription_result.get('response_object')
 | 
			
		||||
        # Check if the subscription was approved and is active
 | 
			
		||||
        if stripe_subscription_obj is None or stripe_subscription_obj.status != 'active':
 | 
			
		||||
            msg = subscription_result.get('error')
 | 
			
		||||
            messages.add_message(self.request, messages.ERROR, msg,
 | 
			
		||||
                                 extra_tags='failed_payment')
 | 
			
		||||
            return HttpResponseRedirect(
 | 
			
		||||
                reverse('hosting:payment') + '#payment_error')
 | 
			
		||||
        user = {
 | 
			
		||||
            'name': self.request.user.name,
 | 
			
		||||
            'email': self.request.user.email,
 | 
			
		||||
            'pass': self.request.user.password,
 | 
			
		||||
            'request_scheme': request.scheme,
 | 
			
		||||
            'request_host': request.get_host(),
 | 
			
		||||
            'language': get_language(),
 | 
			
		||||
        }
 | 
			
		||||
        create_vm_task.delay(vm_template_id, user, specs, template,
 | 
			
		||||
                             stripe_customer_id, billing_address_data,
 | 
			
		||||
                             billing_address_id,
 | 
			
		||||
                             stripe_subscription_obj, card_details_dict)
 | 
			
		||||
 | 
			
		||||
        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,
 | 
			
		||||
            'redirect': reverse('hosting:virtual_machines'),
 | 
			
		||||
            'msg_title': str(_('Thank you for the order.')),
 | 
			
		||||
            'msg_body': str(_('Your VM will be up and running in a few moments.'
 | 
			
		||||
                          ' We will send you a confirmation email as soon as'
 | 
			
		||||
                          ' it is ready.'))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return HttpResponse(json.dumps(response),
 | 
			
		||||
                            content_type="application/json")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OrdersHostingListView(LoginRequiredMixin, ListView):
 | 
			
		||||
    template_name = "hosting/orders.html"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue