Merge pull request #489 from pcoder/task/3772/hosting_billing_monthly_subscription

Task/3772/hosting billing monthly subscription
This commit is contained in:
Arvind Tiwari 2017-09-19 15:01:42 +05:30 committed by GitHub
commit 2223bffa04
6 changed files with 450 additions and 217 deletions

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,6 +18,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\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" msgid "Enter name"
msgstr "Name" msgstr "Name"
@ -183,9 +187,18 @@ msgstr "Kontakt"
msgid "All Rights Reserved" msgid "All Rights Reserved"
msgstr "Alle Rechte vorbehalten" msgstr "Alle Rechte vorbehalten"
msgid "Toggle navigation"
msgstr "Konfiguration"
msgid "Why Data Center Light?" msgid "Why Data Center Light?"
msgstr "Warum Data Center Light?" msgstr "Warum Data Center Light?"
msgid "Login"
msgstr ""
msgid "Dashboard"
msgstr ""
msgid "Finally, an affordable VM hosting in Switzerland!" msgid "Finally, an affordable VM hosting in Switzerland!"
msgstr "Endlich: bezahlbares VM Hosting in der Schweiz" msgstr "Endlich: bezahlbares VM Hosting in der Schweiz"

View file

@ -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.utils.log import get_task_logger
from celery import current_task from celery import current_task
from django.conf import settings 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.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer 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 utils.forms import UserBillingAddressForm
from datetime import datetime from utils.mailer import BaseEmail
from membership.models import StripeCustomer
from django.core.mail import EmailMessage
from utils.models import BillingAddress from utils.models import BillingAddress
from celery.exceptions import MaxRetriesExceededError
logger = get_task_logger(__name__) 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( billing_address = BillingAddress.objects.filter(
id=billing_address_id).first() id=billing_address_id).first()
customer = StripeCustomer.objects.filter(id=stripe_customer_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( vm_id = manager.create_vm(
template_id=vm_template_id, template_id=vm_template_id,
specs=specs, specs=specs,
ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY, ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY,
vm_name="{email}-{template_name}-{date}".format( vm_name=vm_name
email=user.get('email'),
template_name=template.get('name'),
date=int(datetime.now().strftime("%s")))
) )
if vm_id is None: 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 = EmailMessage(**email_data)
email.send() 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: except Exception as e:
logger.error(str(e)) logger.error(str(e))
try: try:

View file

@ -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'); $('#virtual_machine_cancel_form').trigger('submit');
}); });

View file

@ -1,6 +1,8 @@
{% extends "hosting/base_short.html" %} {% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 %} {% load staticfiles bootstrap3 %}
{% load i18n %} {% load i18n %}
{% load custom_tags %}
{% block content %} {% block content %}
<div class="order-detail-container"> <div class="order-detail-container">
@ -20,30 +22,58 @@
<div class="row"> <div class="row">
<div class="col-xs-12 col-md-8 col-md-offset-2"> <div class="col-xs-12 col-md-8 col-md-offset-2">
<div class="invoice-title"> <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> </div>
<hr> <hr>
<div class="row"> <div class="row">
<div class="col-xs-12 col-md-6 pull-right order-confirm-date"> <div class="col-xs-12 col-md-6 pull-right order-confirm-date">
<address> <address>
<strong>{% trans "Date"%}:</strong><br> <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> <strong>{% trans "Status:"%}</strong><br>
{% if order.status == 'Approved' %} {% if order.status == 'Approved' %}
<strong class="text-success">{% trans "Approved" %}</strong> <strong class="text-success">
{% trans "Approved" %}
</strong>
{% else %} {% else %}
<strong class="text-danger">{% trans "Declined" %}</strong> <strong class="text-danger">
{% trans "Declined" %}
</strong>
{% endif %} {% endif %}
<br><br> <br><br>
{% endif %}
</address> </address>
</div> </div>
<div class="col-xs-12 col-md-6"> <div class="col-xs-12 col-md-6">
<address> <address>
<h3><b>{% trans "Billed To:"%}</b></h3> <h3><b>{% trans "Billed To:"%}</b></h3>
{% if order %}
{{user.name}}<br> {{user.name}}<br>
{{order.billing_address.street_address}},{{order.billing_address.postal_code}}<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> </address>
</div> </div>
@ -52,8 +82,15 @@
<div class="col-xs-6"> <div class="col-xs-6">
<address> <address>
<strong>{% trans "Payment Method:"%}</strong><br> <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}} {{user.email}}
{% else %}
{{cc_brand}} {% trans "ending in" %} ****
{{cc_last4}}<br>
{{request.session.user.email}}
{% endif %}
</address> </address>
</div> </div>
</div> </div>
@ -65,31 +102,111 @@
<h3><b>{% trans "Order summary"%}</b></h3> <h3><b>{% trans "Order summary"%}</b></h3>
<hr> <hr>
<div class="content"> <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> <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> <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> <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> </div>
<br/> <br/>
{% url 'hosting:payment' as payment_url %} {% if not order %}
{% if payment_url in request.META.HTTP_REFERER %} <form method="post" id="virtual_machine_create_form">
<div class=" content pull-right"> {% csrf_token %}
<a href="{% url 'hosting:virtual_machines'%}" ><button class="btn btn-info">{% trans "Finish Configuration"%}</button></a> <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>
<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 %} {% endif %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
</div> </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">&times;</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"> <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 () { 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"); locale_date = moment(locale_date).format("YYYY-MM-DD h:mm:ss a");
document.getElementById('order-created_at').innerHTML = locale_date; document.getElementById('order-created_at').innerHTML = locale_date;

View file

@ -21,9 +21,13 @@ urlpatterns = [
url(r'payment/?$', PaymentVMView.as_view(), name='payment'), url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
url(r'settings/?$', SettingsView.as_view(), name='settings'), url(r'settings/?$', SettingsView.as_view(), name='settings'),
url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'), url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'), url(r'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/?$', 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+)/?$', url(r'cancel_order/(?P<pk>\d+)/?$',
OrdersHostingDeleteView.as_view(), name='delete_order'), OrdersHostingDeleteView.as_view(), name='delete_order'),
url(r'create_virtual_machine/?$', CreateVirtualMachinesView.as_view(), url(r'create_virtual_machine/?$', CreateVirtualMachinesView.as_view(),
@ -40,13 +44,16 @@ urlpatterns = [
name='delete_ssh_key'), name='delete_ssh_key'),
url(r'create_ssh_key/?$', SSHKeyCreateView.as_view(), url(r'create_ssh_key/?$', SSHKeyCreateView.as_view(),
name='create_ssh_key'), 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(), url(r'^notifications/(?P<pk>\d+)/?$', MarkAsReadNotificationView.as_view(),
name='read_notification'), name='read_notification'),
url(r'login/?$', LoginView.as_view(), name='login'), url(r'login/?$', LoginView.as_view(), name='login'),
url(r'signup/?$', SignupView.as_view(), name='signup'), url(r'signup/?$', SignupView.as_view(), name='signup'),
url(r'signup-validate/?$', SignupValidateView.as_view(), name='signup-validate'), url(r'signup-validate/?$', SignupValidateView.as_view(),
url(r'reset-password/?$', PasswordResetView.as_view(), name='reset_password'), 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>.+)/$', url(r'reset-password-confirm/(?P<uidb64>[0-9A-Za-z]+)-(?P<token>.+)/$',
PasswordResetConfirmView.as_view(), name='reset_password_confirm'), PasswordResetConfirmView.as_view(), name='reset_password_confirm'),
url(r'^logout/?$', auth_views.logout, url(r'^logout/?$', auth_views.logout,

View file

@ -1,3 +1,4 @@
import json
import logging import logging
import uuid 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.files.base import ContentFile
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse_lazy, reverse
from django.http import Http404 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 redirect
from django.shortcuts import render from django.shortcuts import render
from django.utils.http import urlsafe_base64_decode from django.utils.http import urlsafe_base64_decode
from django.utils.safestring import mark_safe 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, \ from django.views.generic import View, CreateView, FormView, ListView, \
DetailView, \ DetailView, \
DeleteView, TemplateView, UpdateView DeleteView, TemplateView, UpdateView
@ -23,13 +24,13 @@ from stored_messages.api import mark_read
from stored_messages.models import Message from stored_messages.models import Message
from stored_messages.settings import stored_messages_settings from stored_messages.settings import stored_messages_settings
from datacenterlight.tasks import create_vm_task
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from opennebula_api.models import OpenNebulaManager from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer, \ from opennebula_api.serializers import VirtualMachineSerializer, \
VirtualMachineTemplateSerializer VirtualMachineTemplateSerializer
from utils.forms import BillingAddressForm, PasswordResetRequestForm, \ from utils.forms import BillingAddressForm, PasswordResetRequestForm, \
UserBillingAddressForm UserBillingAddressForm
from utils.hosting_utils import get_all_public_keys
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, \ from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, \
@ -606,23 +607,10 @@ class PaymentVMView(LoginRequiredMixin, FormView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.get_form() form = self.get_form()
if form.is_valid(): if form.is_valid():
# Get billing address data # Get billing address data
billing_address_data = form.cleaned_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') token = form.cleaned_data.get('token')
owner = self.request.user owner = self.request.user
# Get or create stripe customer # Get or create stripe customer
customer = StripeCustomer.get_or_create(email=owner.email, customer = StripeCustomer.get_or_create(email=owner.email,
token=token) token=token)
@ -636,115 +624,18 @@ class PaymentVMView(LoginRequiredMixin, FormView):
# Create Billing Address # Create Billing Address
billing_address = form.save() billing_address = form.save()
request.session['billing_address_data'] = billing_address_data
# Make stripe charge to a customer request.session['billing_address'] = billing_address.id
stripe_utils = StripeUtils() request.session['token'] = token
charge_response = stripe_utils.make_charge(amount=final_price, request.session['customer'] = customer.id
customer=customer.stripe_id) return HttpResponseRedirect("{url}?{query_params}".format(
url=reverse('hosting:order-confirmation'),
# 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}),
query_params='page=payment')) query_params='page=payment'))
else: else:
return self.form_invalid(form) return self.form_invalid(form)
class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin, class OrdersHostingDetailView(LoginRequiredMixin,
DetailView): DetailView):
template_name = "hosting/order_detail.html" template_name = "hosting/order_detail.html"
context_object_name = "order" context_object_name = "order"
@ -752,18 +643,42 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin,
permission_required = ['view_hostingorder'] permission_required = ['view_hostingorder']
model = 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): def get_context_data(self, **kwargs):
# Get context # Get context
context = super(DetailView, self).get_context_data(**kwargs) context = super(DetailView, self).get_context_data(**kwargs)
obj = self.get_object() obj = self.get_object()
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.email, if 'specs' not in self.request.session:
password=owner.password) 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': if self.request.GET.get('page', '') == 'payment':
context['page_header_text'] = _('Confirm Order') context['page_header_text'] = _('Confirm Order')
else: else:
context['page_header_text'] = _('Invoice') context['page_header_text'] = _('Invoice')
if obj is not None:
try: try:
manager = OpenNebulaManager(email=owner.email,
password=owner.password)
vm = manager.get_vm(obj.vm_id) vm = manager.get_vm(obj.vm_id)
context['vm'] = VirtualMachineSerializer(vm).data context['vm'] = VirtualMachineSerializer(vm).data
except WrongIdError: except WrongIdError:
@ -775,11 +690,98 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin,
context['error'] = 'WrongIdError' context['error'] = 'WrongIdError'
except ConnectionRefusedError: except ConnectionRefusedError:
messages.error(self.request, 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 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): class OrdersHostingListView(LoginRequiredMixin, ListView):
template_name = "hosting/orders.html" template_name = "hosting/orders.html"