Merge branch 'master' into task/3785/my_bills_page

This commit is contained in:
Arvind Tiwari 2017-09-28 18:38:49 +05:30
commit 7bd61f708c
39 changed files with 1141 additions and 698 deletions

1
.gitignore vendored
View file

@ -36,3 +36,4 @@ secret-key
.env .env
*.mo *.mo
*.log *.log
*.sql

View file

@ -1,4 +1,11 @@
Pre-changelog: 1.2.3 2017-09-20 Next:
* #3764: [hosting] Show cancelled VMs' invoices
* #3736: [dcl] Refactor the place where we compute the VM price
* #3730: [dcl] Refactor price parameter passed in the DCL flow
* #3807: [dcl] Remove PricingView as it is no more used
* #3813: [hosting] JS error in create ssh key page
* #3756: [dcl] Update landing calculator and billing info page
1.2.3: 2017-09-25
* #3484: [dcl, hosting] Refactored account activation, password reset, VM order and cancellation email * #3484: [dcl, hosting] Refactored account activation, password reset, VM order and cancellation email
* #3731: [dcl, hosting] Added cdist ssh key handler * #3731: [dcl, hosting] Added cdist ssh key handler
* #3628: [dcl] on hosting, VM is created at credit card info submit * #3628: [dcl] on hosting, VM is created at credit card info submit
@ -6,7 +13,9 @@ Pre-changelog: 1.2.3 2017-09-20
* #3786: [hosting] Redesigned the hosting invoice and order-confirmation page * #3786: [hosting] Redesigned the hosting invoice and order-confirmation page
* #3728: [hosting] VM Termination animation added * #3728: [hosting] VM Termination animation added
* #3777: [hosting] Create new VM calculator added like dcl landing * #3777: [hosting] Create new VM calculator added like dcl landing
* #3781: [hosting] Resend activation mail
* #3806: [hosting] Fix can not create VMs after password reset * #3806: [hosting] Fix can not create VMs after password reset
* #3812: [hosting] Modal check icon made thin and font-size fixed
* Feature: [cms, blog] Added /cms prefix for all the django-cms generated urls * Feature: [cms, blog] Added /cms prefix for all the django-cms generated urls
* Bugfix: [dcl, hosting] added host to celery error mails * Bugfix: [dcl, hosting] added host to celery error mails
* Bugfix: [ungleich] Fixed wrong subdomain digitalglarus.ungleich.ch * Bugfix: [ungleich] Fixed wrong subdomain digitalglarus.ungleich.ch

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-16 14:09+0000\n" "POT-Creation-Date: 2017-09-23 21:22+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"
@ -65,24 +65,6 @@ msgstr "Bitte gib einen Wert von 10 bis 200 ein."
msgid "GB Storage (SSD)" msgid "GB Storage (SSD)"
msgstr "GB Storage (SSD)" msgstr "GB Storage (SSD)"
msgid "Name"
msgstr ""
msgid "Your Name"
msgstr "Dein Name"
msgid "Please enter your name."
msgstr "Bitte gib Deinen Namen ein."
msgid "Email"
msgstr "E-Mail-Adresse"
msgid "Your Email"
msgstr "Deine E-Mail"
msgid "Please enter a valid email address."
msgstr "Bitte gib eine gültige E-Mailadresse ein."
msgid "Continue" msgid "Continue"
msgstr "Weiter" msgstr "Weiter"
@ -95,6 +77,18 @@ msgstr "Vielen Dank für Deine Nachricht."
msgid "Get in touch with us!" msgid "Get in touch with us!"
msgstr "Sende uns eine Nachricht." msgstr "Sende uns eine Nachricht."
msgid "Name"
msgstr ""
msgid "Please enter your name."
msgstr "Bitte gib Deinen Namen ein."
msgid "Email"
msgstr "E-Mail-Adresse"
msgid "Please enter a valid email address."
msgstr "Bitte gib eine gültige E-Mailadresse ein."
msgid "Message" msgid "Message"
msgstr "Nachricht" msgstr "Nachricht"
@ -151,6 +145,15 @@ msgstr ""
"Adressleiste deines Browsers.<br/>\n" "Adressleiste deines Browsers.<br/>\n"
"%(base_url)s%(activation_link)s\n" "%(base_url)s%(activation_link)s\n"
#, python-format
msgid ""
"Your account details are as follows:<br/><br/>\n"
"Username : Your email address<br/>\n"
"Password : %(account_details)s<br/><br/>\n"
"You can reset your password here:\n"
"%(base_url)s%(reset_password_url)s\n"
msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"You can activate your Data Center Light account by clicking here.\n" "You can activate your Data Center Light account by clicking here.\n"
@ -163,6 +166,17 @@ msgstr ""
"den folgenden Link in die Adressleiste deines Browsers.\n" "den folgenden Link in die Adressleiste deines Browsers.\n"
"%(base_url)s%(activation_link)s\n" "%(base_url)s%(activation_link)s\n"
#, python-format
msgid ""
"Your account details are as follows:\n"
"\n"
"Username : Your email address\n"
"Password : %(account_details)s\n"
"\n"
"You can reset your password here:\n"
"%(base_url)s%(reset_password_url)s\n"
msgstr ""
msgid "Home" msgid "Home"
msgstr "Home" msgstr "Home"
@ -264,6 +278,45 @@ msgstr "Kontaktiere uns"
msgid "Switzerland " msgid "Switzerland "
msgstr "Schweiz " msgstr "Schweiz "
msgid "Welcome back"
msgstr "Willkommen zurück"
msgid ""
"Review your billing address and card details and proceed to make payment."
msgstr ""
"Überprüfe die Rechnungsadresse und Kreditkartendaten und fahre mit der "
"Zahlung fort."
msgid "Log in"
msgstr "Anmelden"
msgid ""
"Already signed up?<br>By logging in you can retrieve saved billing "
"information."
msgstr ""
"Bereits eingeloggt? Nach der Anmeldung kannst Du gespeicherte "
"Rechnungsinformationen abrufen."
msgid "LOGIN"
msgstr "ANMELDEN"
msgid "Don't have an account yet?"
msgstr "Besitzt du kein Benutzerkonto?"
msgid "You can sign up by filling in the information below."
msgstr ""
"Du kannst Dich anmelden, indem Du die die untenstehenden Informationen "
"ausfüllst."
msgid "Forgot password?"
msgstr "Passwort vergessen?"
msgid "Sign up"
msgstr "Registrieren"
msgid "Billing Address"
msgstr "Rechnungsadresse"
msgid "Your Order" msgid "Your Order"
msgstr "Deine Bestellung" msgstr "Deine Bestellung"
@ -288,23 +341,15 @@ msgstr "inkl. Mehrwertsteuer"
msgid "Month" msgid "Month"
msgstr "Monat" msgstr "Monat"
msgid "Billing Address"
msgstr "Rechnungsadresse"
msgid "Credit Card" msgid "Credit Card"
msgstr "Kreditkarte" msgstr "Kreditkarte"
msgid "" msgid ""
"\n" "Please fill in your credit card information below. We are using <a href="
" Please fill in your credit card information " "\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do not "
"below. We are using <a\n" "store your information in our database."
" href=\"https://stripe.com\" target="
"\"_blank\">Stripe</a> for payment and do not store\n"
" your information in our database.\n"
" "
msgstr "" msgstr ""
"\n" "Bitte fülle Deine Kreditkarteninformationen unten aus. Wir nutzen <a href="
"Bitte füll Deine Kreditkarteninformationen unten aus. Wir nutzen <a href="
"\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und " "\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und "
"speichern keine Informationen in unserer Datenbank." "speichern keine Informationen in unserer Datenbank."
@ -330,6 +375,13 @@ msgstr ""
msgid "Card Type" msgid "Card Type"
msgstr "Kartentyp" msgstr "Kartentyp"
msgid ""
"You are not making any payment yet. After placing your order, you will be "
"taken to the Submit Payment Page."
msgstr ""
"Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst, "
"nachdem Du die Bestellung auf der nächsten Seite bestätigt hast."
msgid "Processing" msgid "Processing"
msgstr "Weiter" msgstr "Weiter"
@ -365,6 +417,15 @@ msgstr ""
msgid "Place order" msgid "Place order"
msgstr "Bestellen" msgstr "Bestellen"
msgid "Processing..."
msgstr "Abarbeitung..."
msgid "Hold tight, we are processing your request"
msgstr "Bitte warten - wir verbeiten Deine Anfrage gerade"
msgid "Some problem encountered. Please try again later."
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
msgid "We are cutting down the costs significantly!" msgid "We are cutting down the costs significantly!"
msgstr "Wir sorgen dafür, dass die Kosten für Dich signifikant abnehmen" msgstr "Wir sorgen dafür, dass die Kosten für Dich signifikant abnehmen"
@ -479,11 +540,38 @@ msgstr "Ungültige RAM-Grösse"
msgid "Invalid storage size" msgid "Invalid storage size"
msgstr "Ungültige Speicher-Grösse" msgstr "Ungültige Speicher-Grösse"
msgid "is not a proper name" msgid "Error."
msgstr "ist kein gültiger Name" msgstr ""
msgid "is not a proper email" msgid ""
msgstr "ist keine gültige E-Mailadresse" "There was a payment related error. On close of this popup, you will be "
"redirected back to the payment page."
msgstr ""
msgid "Thank you for the order."
msgstr "Danke für Deine Bestellung."
msgid ""
"Your VM will be up and running in a few moments. We will send you a "
"confirmation email as soon as it is ready."
msgstr ""
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
"auf sie zugreifen kannst."
#~ msgid "Email Address"
#~ msgstr "E-Mail-Adresse"
#~ msgid "is not a proper name"
#~ msgstr "ist kein gültiger Name"
#~ msgid "is not a proper email"
#~ msgstr "ist keine gültige E-Mailadresse"
#~ msgid "Your Name"
#~ msgstr "Dein Name"
#~ msgid "Your Email"
#~ msgstr "Deine E-Mail"
#~ msgid "" #~ msgid ""
#~ "\n" #~ "\n"

View file

@ -1653,3 +1653,20 @@ a.list-group-item-danger.active:focus {
.panel-danger > .panel-heading .badge { .panel-danger > .panel-heading .badge {
background-color: #eb4d5c; background-color: #eb4d5c;
} }
.checkmark {
display: inline-block;
}
.checkmark:after {
/*Add another block-level blank space*/
content: '';
display: block;
/*Make it a small rectangle so the border will create an L-shape*/
width: 25px;
height: 60px;
/*Add a white border on the bottom and left, creating that 'L' */
border: solid #777;
border-width: 0 3px 3px 0;
/*Rotate the L 45 degrees to turn it into a checkmark*/
transform: rotate(45deg);
}

View file

@ -155,9 +155,7 @@
function _calcPricing() { function _calcPricing() {
var total = (cardPricing['cpu'].value * 5) + (2 * cardPricing['ram'].value) + (0.6 * cardPricing['storage'].value); var total = (cardPricing['cpu'].value * 5) + (2 * cardPricing['ram'].value) + (0.6 * cardPricing['storage'].value);
total = parseFloat(total.toFixed(2)); total = parseFloat(total.toFixed(2));
$("#total").text(total); $("#total").text(total);
$('input[name=total]').val(total);
} }
function form_success() { function form_success() {
@ -191,4 +189,4 @@
}); });
}) })
} }
})(jQuery); })(jQuery);

View file

@ -13,7 +13,7 @@ from hosting.models import HostingOrder, HostingBill
from membership.models import StripeCustomer, CustomUser 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 utils.hosting_utils import get_all_public_keys from utils.hosting_utils import get_all_public_keys, get_or_create_vm_detail
from utils.forms import UserBillingAddressForm from utils.forms import UserBillingAddressForm
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.models import BillingAddress from utils.models import BillingAddress
@ -52,7 +52,8 @@ def create_vm_task(self, vm_template_id, user, specs, template,
stripe_customer_id, billing_address_data, stripe_customer_id, billing_address_data,
billing_address_id, billing_address_id,
charge, cc_details): charge, cc_details):
logger.debug("Running create_vm_task on {}".format(current_task.request.hostname)) logger.debug(
"Running create_vm_task on {}".format(current_task.request.hostname))
vm_id = None vm_id = None
try: try:
final_price = specs.get('price') final_price = specs.get('price')
@ -142,9 +143,10 @@ def create_vm_task(self, vm_template_id, user, specs, template,
email.send() email.send()
if 'pass' in user: if 'pass' in user:
lang = 'en-us' lang = 'en-us'
if user.get('language') is not None: if user.get('language') is not None:
logger.debug("Language is set to {}".format(user.get('language'))) logger.debug(
"Language is set to {}".format(user.get('language')))
lang = user.get('language') lang = user.get('language')
translation.activate(lang) translation.activate(lang)
# Send notification to the user as soon as VM has been booked # Send notification to the user as soon as VM has been booked
@ -174,6 +176,7 @@ def create_vm_task(self, vm_template_id, user, specs, template,
logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
if new_host is not None: if new_host is not None:
custom_user = CustomUser.objects.get(email=user.get('email')) custom_user = CustomUser.objects.get(email=user.get('email'))
get_or_create_vm_detail(custom_user, manager, vm_id)
if custom_user is not None: if custom_user is not None:
public_keys = get_all_public_keys(custom_user) public_keys = get_all_public_keys(custom_user)
keys = [{'value': key, 'state': True} for key in keys = [{'value': key, 'state': True} for key in

View file

@ -8,7 +8,7 @@
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="modal-icon"><i class="fa fa-check" aria-hidden="true"></i></div> <div class="modal-icon"><i class="checkmark" aria-hidden="true"></i></div>
<h4 class="modal-title">{% trans "Request Sent" %}</h4> <h4 class="modal-title">{% trans "Request Sent" %}</h4>
<p class="modal-text">{% trans "Thank you for your subscription! You will receive a confirmation mail from our team" %}</p> <p class="modal-text">{% trans "Thank you for your subscription! You will receive a confirmation mail from our team" %}</p>
</div> </div>

View file

@ -77,47 +77,9 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<input type="hidden" name="total">
<!--<div class="description check-ip"> <!--<div class="description check-ip">
<input type="checkbox" name="ipv6"> Ipv6 Only<br> <input type="checkbox" name="ipv6"> Ipv6 Only<br>
</div>--> </div>-->
<div class="form-group">
<div class="description input justify-center">
<label for="name" class="control-label">{% trans "Name"%}</label>
<input type="text" name="name" class="form-control" placeholder="{% trans 'Your Name'%}"
data-minlength="3" data-error="{% trans 'Please enter your name.' %}" required>
</div>
<div class="help-block with-errors">
{% for message in messages %}
{% if 'name' in message.tags %}
<ul class="list-unstyled">
<li>
{{ message|safe }}
</li>
</ul>
{% endif %}
{% endfor %}
</div>
</div>
<div class="form-group">
<div class="description input justify-center">
<label for="email" class="control-label">{% trans "Email"%}</label>
<input name="email" type="email" pattern="^[^@\s]+@([^@\s]+\.)+[^@\s]+$" class="form-control"
placeholder="{% trans 'Your Email' %}"
data-error="{% trans 'Please enter a valid email address.' %}" required>
</div>
<div class="help-block with-errors">
{% for message in messages %}
{% if 'email' in message.tags %}
<ul class="list-unstyled">
<li>
{{ message|safe }}
</li>
</ul>
{% endif %}
{% endfor %}
</div>
</div>
</div> </div>
<input type="submit" class="btn btn-primary disabled" value="{% trans 'Continue' %}"></input> <input type="submit" class="btn btn-primary disabled" value="{% trans 'Continue' %}"></input>
</form> </form>

View file

@ -11,4 +11,14 @@ You can also copy and paste the following link into the address bar of your brow
to activate your Data Center Light account.<br/> to activate your Data Center Light account.<br/>
{{base_url}}{{activation_link}} {{base_url}}{{activation_link}}
{% endblocktrans %} {% endblocktrans %}
{% if account_details %}
{% url 'hosting:reset_password' as reset_password_url %}
<br/><br/>
{% blocktrans %}Your account details are as follows:<br/><br/>
Username : Your email address<br/>
Password : {{account_details}}<br/><br/>
You can reset your password here:
{{base_url}}{{reset_password_url}}
{% endblocktrans %}
{% endif %}
{% endblock %} {% endblock %}

View file

@ -7,4 +7,15 @@ You can also copy and paste the following link into the address bar of your brow
to activate your Data Center Light account. to activate your Data Center Light account.
{{base_url}}{{activation_link}} {{base_url}}{{activation_link}}
{% endblocktrans %} {% endblocktrans %}
{% if account_details %}
{% url 'hosting:reset_password' as reset_password_url %}
{% blocktrans %}Your account details are as follows:
Username : Your email address
Password : {{account_details}}
You can reset your password here:
{{base_url}}{{reset_password_url}}
{% endblocktrans %}
{% endif %}
{% endblock %} {% endblock %}

View file

@ -1,190 +1,182 @@
{% extends "hosting/base_short.html" %} {% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3 i18n %} {% load staticfiles bootstrap3 i18n %}
{% block css_extra %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/paymentfont/1.1.2/css/paymentfont.min.css"/>
{% endblock css_extra %}
{% block navbar %} {% block navbar %}
{% include "datacenterlight/includes/_navbar.html" %} {% include "datacenterlight/includes/_navbar.html" %}
{% endblock navbar %} {% endblock navbar %}
{% block content %} {% block content %}
<!-- Credit card form --> <!-- Credit card form -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/paymentfont/1.1.2/css/paymentfont.min.css"/>
<div class="dcl-order-container"> <div class="dcl-order-container">
<div class="payment-container"> <div class="payment-container">
<div class="row"> <div class="dcl-payment-grid">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 dcl-order-sec"> <div class="dcl-payment-box">
<h3><strong>{%trans "Your Order" %}</strong></h3> <div class="dcl-payment-section">
<div class="col-xs-6 col-sm-12 col-md-12 col-lg-12 dcl-order-table-header"> {% if request.user.is_authenticated %}
<div class="col-xs-12 col-sm-2 col-md-1 col-lg-1 tbl-header"> <div class="dcl-payment-user">
{%trans "Cores" %} <h4>{% trans "Welcome back" %} {{request.user.name}}!</h4>
</div> <p>{% trans "Review your billing address and card details and proceed to make payment." %}</p>
<div class="col-xs-12 col-sm-3 col-md-4 col-lg-4 tbl-header">
{%trans "Memory" %}
</div>
<div class="col-xs-12 col-sm-3 col-md-3 col-lg-3 tbl-header">
{%trans "Disk space" %}
</div>
<div class="col-xs-12 col-sm-4 col-md-4 col-lg-4 tbl-header">
{%trans "Configuration" %}
</div>
</div>
<div class="col-xs-6 col-sm-12 col-md-12 col-lg-12 dcl-order-table-content">
<div class="col-xs-12 col-sm-2 col-md-1 col-lg-1 tbl-content">
{{request.session.specs.cpu|floatformat}}
</div>
<div class="col-xs-12 col-sm-3 col-md-4 col-lg-4 tbl-content">
{{request.session.specs.memory|floatformat}} GB
</div>
<div class="col-xs-12 col-sm-3 col-md-3 col-lg-3 tbl-content">
{{request.session.specs.disk_size|floatformat}} GB
</div>
<div class="col-xs-12 col-sm-4 col-md-4 col-lg-4 tbl-content">
{{request.session.template.name}}
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 dcl-order-table-total">
<div class="col-xs-6 col-sm-6 col-md-6 col-lg-6 tbl-tot tbl-no-padding">
{%trans "Total" %} <span>{%trans "including VAT" %}</span>
</div>
<div class="col-xs-6 col-sm-6 col-md-6 col-lg-6 tbl-no-padding">
<div class="col-xs-12 col-sm-4 col-md-4 col-lg-4"></div>
<div class="col-xs-12 col-sm-6 col-md-6 col-lg-6 tbl-total">{{request.session.specs.price}}
CHF<span class="dcl-price-month">/{% trans "Month" %}</span>
</div> </div>
</div> {% else %}
<h3>{%trans "Log in" %}</h3>
<hr class="top-hr">
<p style="margin-bottom: 20px;">{% blocktrans %}Already signed up?<br>By logging in you can retrieve saved billing information.{% endblocktrans %}</p>
<form role="form" id="login-form" method="post" action="{% url 'hosting:login' %}" novalidate>
{% for field in login_form %}
{% csrf_token %}
{% bootstrap_field field show_label=False type='fields'%}
{% endfor %}
<input type='hidden' name='next' value='{{request.path}}'/>
<div class="form-group text-right">
<button type="submit" class="btn btn-wide btn-vm-contact">{% trans "LOGIN" %}</button>
</div>
</form>
<p>
{% trans "Don't have an account yet?" %}<br>
{% trans "You can sign up by filling in the information below." %}<br>
<a href="{% url 'hosting:reset_password' %}">{% trans "Forgot password?" %}</a>
</p>
{% endif %}
</div> </div>
</div> </div>
</div> <div class="dcl-payment-box">
<div class="row"> <div class="dcl-payment-section">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 dcl-billing-sec"> {% if not request.user.is_authenticated %}
<div class="col-xs-12 col-sm-5 col-md-6 billing dcl-billing"> <h3><b>{%trans "Sign up"%}</b></h3>
<h3><b>{%trans "Billing Address"%}</b></h3> {% else %}
<hr> <h3><b>{%trans "Billing Address"%}</b></h3>
{% endif %}
<hr class="top-hr">
{% for message in messages %}
{% if 'duplicate_email' in message.tags %}
<p class="text-danger">{{message}}</p>
{% endif %}
{% endfor %}
<form role="form" id="billing-form" method="post" action="" novalidate> <form role="form" id="billing-form" method="post" action="" novalidate>
{% for field in form %}
{% csrf_token %} {% csrf_token %}
{% for field in form %}
{% bootstrap_field field show_label=False type='fields'%} {% bootstrap_field field show_label=False type='fields'%}
{% endfor %} {% endfor %}
</form> </form>
</div> </div>
<div class="col-xs-12 col-sm-7 col-md-6 creditcard-box dcl-creditcard"> </div>
<div class="dcl-payment-box">
<div class="dcl-payment-section">
<h3>{%trans "Your Order" %}</h3>
<hr class="top-hr">
<div class="dcl-payment-order">
<p>{% trans "Cores"%} <strong class="pull-right">{{request.session.specs.cpu|floatformat}}</strong></p>
<hr>
<p>{% trans "Memory"%} <strong class="pull-right">{{request.session.specs.memory|floatformat}} GB</strong></p>
<hr>
<p>{% trans "Disk space"%} <strong class="pull-right">{{request.session.specs.disk_size|floatformat}} GB</strong></p>
<hr>
<p>{% trans "Configuration"%} <strong class="pull-right">{{request.session.template.name}}</strong></p>
<hr>
<p class="last-p"><strong>{%trans "Total" %}</strong>&nbsp;&nbsp;<small>({%trans "including VAT" %})</small> <strong class="pull-right">{{request.session.specs.price}} CHF/{% trans "Month" %}</strong></p>
</div>
</div>
</div>
<div class="dcl-payment-box">
<div class="dcl-payment-section">
<h3><b>{%trans "Credit Card"%}</b></h3> <h3><b>{%trans "Credit Card"%}</b></h3>
<hr> <hr class="top-hr">
<p>
{% blocktrans %}Please fill in your credit card information below. We are using <a href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store your information in our database.{% endblocktrans %}
</p>
<div> <div>
<div> {% if credit_card_data.last4 %}
<p>
{% blocktrans %}
Please fill in your credit card information below. We are using <a
href="https://stripe.com" target="_blank">Stripe</a> for payment and do not store
your information in our database.
{% endblocktrans %}
</p>
</div>
<br>
<div>
{% if credit_card_data.last4 %}
<form role="form" id="payment-form-with-creditcard" novalidate> <form role="form" id="payment-form-with-creditcard" novalidate>
<h5 class="billing-head">Credit Card</h5> <h5 class="billing-head">Credit Card</h5>
<h5 class="membership-lead">Last 4: *****{{credit_card_data.last4}}</h5> <h5 class="membership-lead">Last 4: *****{{credit_card_data.last4}}</h5>
<h5 class="membership-lead">Type: {{credit_card_data.cc_brand}}</h5> <h5 class="membership-lead">Type: {{credit_card_data.cc_brand}}</h5>
<input type="hidden" name="credit_card_needed" value="false"/> <input type="hidden" name="credit_card_needed" value="false"/>
</form> </form>
<div class="row"> {% if not messages and not form.non_field_errors %}
<div class="col-xs-12"> <p class="card-warning-content card-warning-addtional-margin">
{% if not messages and not form.non_field_errors %} {% trans "You are not making any payment yet. After submitting your card information, you will be taken to the Confirm Order Page." %}
<p class="card-warning-content card-warning-addtional-margin"> </p>
{% trans "You are not making any payment yet. After submitting your card information, you will be taken to the Confirm Order Page." %} {% endif %}
</p> <div id='payment_error'>
{% endif %} {% for message in messages %}
<div id='payment_error'> {% if 'failed_payment' or 'make_charge_error' in message.tags %}
{% for message in messages %} <ul class="list-unstyled"><li>
{% if 'failed_payment' or 'make_charge_error' in message.tags %} <p class="card-warning-content card-warning-error">{{ message|safe }}</p>
<ul class="list-unstyled"><li> </li></ul>
<p class="card-warning-content card-warning-error">{{ message|safe }}</p> {% endif %}
</li></ul> {% endfor %}
{% endif %} {% for error in form.non_field_errors %}
{% endfor %} <p class="card-warning-content card-warning-error">
{% for error in form.non_field_errors %} {{ error|escape }}
<p class="card-warning-content card-warning-error"> </p>
{{ error|escape }} {% endfor %}
</p>
{% endfor %}
</div>
</div>
<div class="col-xs-12">
<div class="col-xs-6 pull-right">
<button id="payment_button_with_creditcard" class="btn btn-success stripe-payment-btn"
type="submit">
{%trans "Submit" %}
</button>
</div>
</div>
</div> </div>
<div class="text-right">
{% else %} <button id="payment_button_with_creditcard" class="btn btn-success btn-vm-contact" type="submit">{%trans "Submit" %}</button>
</div>
{% else %}
<form action="" id="payment-form-new" method="POST"> <form action="" id="payment-form-new" method="POST">
<input type="hidden" name="token"/> <input type="hidden" name="token"/>
<div class="group"> <div class="group">
<div class="col-xs-12 col-sm-12 col-md-10 col-lg-9 credit-card-goup"> <div class="credit-card-goup">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 card-element card-number-element"> <div class="card-element card-number-element">
<label>{%trans "Card Number" %}</label> <label>{%trans "Card Number" %}</label>
<div id="card-number-element" class="field my-input"></div> <div id="card-number-element" class="field my-input"></div>
</div> </div>
<div class="col-xs-5 col-sm-3 col-md-3 col-lg-3 card-element card-expiry-element"> <div class="row">
<label>{%trans "Expiry Date" %}</label> <div class="col-xs-5 card-element card-expiry-element">
<div id="card-expiry-element" class="field my-input"></div> <label>{%trans "Expiry Date" %}</label>
<div id="card-expiry-element" class="field my-input"></div>
</div>
<div class="col-xs-3 col-xs-offset-4 card-element card-cvc-element">
<label>{%trans "CVC" %}</label>
<div id="card-cvc-element" class="field my-input"></div>
</div>
</div> </div>
<div class="col-xs-12 col-sm-2 col-md-6 col-lg-7 hide-mobile"></div> <div class="card-element brand">
<div class="col-xs-3 col-sm-3 col-md-3 col-lg-2 card-element card-cvc-element">
<label>{%trans "CVC" %}</label>
<div id="card-cvc-element" class="field my-input"></div>
</div>
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 card-element brand">
<label>{%trans "Card Type" %}</label> <label>{%trans "Card Type" %}</label>
<i class="pf pf-credit-card" id="brand-icon"></i> <i class="pf pf-credit-card" id="brand-icon"></i>
</div> </div>
</div> </div>
</div> </div>
<div id="card-errors" role="alert"></div> <div id="card-errors"></div>
<div class="row"> {% if not messages and not form.non_field_errors %}
<div class="col-xs-12"> <p class="card-warning-content">
{% if not messages and not form.non_field_errors %} {% trans "You are not making any payment yet. After placing your order, you will be taken to the Submit Payment Page." %}
</p>
{% endif %}
<div id='payment_error'>
{% for message in messages %}
{% if 'failed_payment' in message.tags or 'make_charge_error' in message.tags %}
<ul class="list-unstyled">
<li><p class="card-warning-content card-warning-error">{{ message|safe }}</p></li>
</ul>
{% elif not form.non_field_errors %}
<p class="card-warning-content"> <p class="card-warning-content">
{% trans "You are not making any payment yet. After submitting your card information, you will be taken to the Confirm Order Page." %} {% trans "You are not making any payment yet. After placing your order, you will be taken to the Submit Payment Page." %}
</p> </p>
{% endif %} {% endif %}
<div id='payment_error'> {% endfor %}
{% for message in messages %}
{% if 'failed_payment' or 'make_charge_error' in message.tags %}
<ul class="list-unstyled"><li>
<p class="card-warning-content card-warning-error">{{ message|safe }}</p>
</li></ul>
{% endif %}
{% endfor %}
{% for error in form.non_field_errors %} {% for error in form.non_field_errors %}
<p class="card-warning-content card-warning-error"> <p class="card-warning-content card-warning-error">
{{ error|escape }} {{ error|escape }}
</p> </p>
{% endfor %} {% endfor %}
</div> </div>
</div> <div class="text-right">
<div class="col-xs-12"> <button class="btn btn-vm-contact btn-wide" type="submit">{%trans "SUBMIT" %}</button>
<div class="col-xs-6 pull-right">
<button class="btn btn-success stripe-payment-btn" type="submit">{%trans "Submit" %}
</button>
</div>
</div>
</div> </div>
<div class="row" style="display:none;"> <div style="display:none;">
<div class="col-xs-12"> <p class="payment-errors"></p>
<p class="payment-errors"></p>
</div>
</div> </div>
</form> </form>
{% endif %}
{% endif %}
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -9,98 +9,135 @@
{% block content %} {% block content %}
<div class="order-detail-container"> <div class="order-detail-container">
{% if messages %} {% if messages %}
<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">
<br/> <br/>
<div class="alert alert-warning"> <div class="alert alert-warning">
{% for message in messages %} {% for message in messages %}
<span>{{ message }}</span> <span>{{ message }}</span>
{% endfor %} {% endfor %}
</div>
</div> </div>
</div> </div>
{% endif %} </div>
{% if not error %} {% endif %}
<div class="row"> {% if not error %}
<div class="col-xs-12 col-md-8 col-md-offset-2"> <div class="row">
<div class="invoice-title"> <div class="col-xs-12 col-md-8 col-md-offset-2">
<h2>{% trans "Confirm Order"%}</h2> <div class="invoice-title">
</div> <h2>{% trans "Confirm Order"%}</h2>
<hr>
<div class="row">
<div class="col-xs-12 col-sm-6 pull-right order-confirm-date">
<address>
<strong>{% trans "Date"%}:</strong><br>
<span id="order-created_at">{% now "Y-m-d H:i" %}</span><br><br>
</address>
</div>
<div class="col-xs-12 col-sm-6">
<address>
<h3><b>{% trans "Billed To:"%}</b></h3>
{% 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 %}
</address>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<address>
<strong>{% trans "Payment Method:"%}</strong><br>
{{cc_brand}} {% trans "ending in" %} **** {{cc_last4}}<br>
{{request.session.user.email}}
</address>
</div>
</div>
</div> </div>
</div> <hr>
<div class="row">
<div class="row"> <div class="col-xs-12 col-sm-6 pull-right order-confirm-date">
<div class="col-md-8 col-md-offset-2"> <address>
<h3><b>{% trans "Order summary"%}</b></h3> <strong>{% trans "Date"%}:</strong><br>
<hr> <span id="order-created_at">{% now "Y-m-d H:i" %}</span><br><br>
<div class="content"> </address>
{% with request.session.specs as vm %} </div>
<p><b>{% trans "Cores"%}</b> <span class="pull-right">{{vm.cpu}}</span></p> <div class="col-xs-12 col-sm-6">
<hr> <address>
<p><b>{% trans "Memory"%}</b> <span class="pull-right">{{vm.memory}} GB</span></p> <h3><b>{% trans "Billed To:"%}</b></h3>
<hr> {% with billing_address_data as billing_address %}
<p><b>{% trans "Disk space"%}</b> <span class="pull-right">{{vm.disk_size}} GB</span></p> {{billing_address.cardholder_name}}<br> {{billing_address.street_address}}, {{billing_address.postal_code}}<br>
<hr> {{billing_address.city}}, {{billing_address.country}}.
<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 %} {% endwith %}
</address>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<address>
<strong>{% trans "Payment Method:"%}</strong><br>
{{cc_brand}} {% trans "ending in" %} **** {{cc_last4}}<br>
{{request.session.user.email}}
</address>
</div> </div>
<br/>
<form method="post">
{% 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">
<a href="{{next_url}}" ><button class="btn btn-info pull-right">{% trans "Place order"%}</button></a>
</div>
</div>
</form>
</div> </div>
</div> </div>
{% endif %} </div>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h3><b>{% trans "Order summary"%}</b></h3>
<hr>
<div class="content">
{% 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>
<hr>
<p><b>{% trans "Disk space"%}</b> <span class="pull-right">{{vm.disk_size}} GB</span></p>
<hr>
<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 %}
</div>
<br/>
<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>
</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">&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">
<a id="createvm-modal-done-btn" class="btn btn-success btn-ok btn-wide hide" href="">{% trans "OK" %}</a>
<button id="createvm-modal-close-btn" type="button" class="btn btn-danger btn-ok btn-wide hide" data-dismiss="modal" aria-label="create-vm-close">{% trans "Close" %}</button>
</div>
</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;
};
}; </script>
</script>
{%endblock%} {%endblock%}

View file

@ -1,96 +0,0 @@
{% extends "datacenterlight/base.html" %}
{% load staticfiles i18n%}
{% get_current_language as LANGUAGE_CODE %}
{% block content %}
<div class="intro-pricing">
<div class="intro-message">
<h2 class="section-heading">{% trans "We are cutting down the costs significantly!" %}</h2>
</div>
</div>
<div class="price-calc-section">
<div class="card">
<img class="img-beta" src="{% static 'datacenterlight/img/beta-img.png' %}" alt="">
<div class="caption">
<form method="POST" action="">
{% csrf_token %}
<div class="title">
<h3>{% trans "VM hosting" %} </h3>
</div>
<div class="price">
<span id="total">15</span>
<span>CHF</span>
<div class="price-text">
<p>{% trans "VAT included" %}</p>
</div>
</div>
<div class="descriptions">
<div class="description">
<p>{% trans "Hosted in Switzerland" %}</p>
</div>
<div class="description">
<i class="fa fa-minus-circle left" data-minus="cpu" aria-hidden="true"></i>
<input class="input-price" type="number" min="1" max="42" id="coreValue" name="cpu">
<span> Core</span>
<i class="fa fa-plus-circle right" data-plus="cpu" aria-hidden="true"></i>
</div>
<div class="description">
<i class="fa fa-minus-circle left" data-minus="ram" aria-hidden="true"></i>
<input id="ramValue" class="input-price" type="number" min="2" max="200" name="ram">
<span> GB RAM</span>
<i class="fa fa-plus-circle right" data-plus="ram" aria-hidden="true"></i>
</div>
<div class="description">
<i class="fa fa-minus-circle left" data-minus="storage" aria-hidden="true"></i>
<input id="storageValue" class="input-price" type="number" min="10" max="500" step="10" name="storage">
<span>{% trans "GB Storage (SSD)" %}</span>
<i class="fa fa-plus-circle right" data-plus="storage" aria-hidden="true"></i>
</div>
<div class="description select-configuration input">
<label for="name">OS</label>
<select name="config" id="">
{% for template in templates %}
<option value="{{template.id}}">{{template.name}} </option>
{% endfor %}
</select>
</div>
<input type="hidden" name="total">
<!-- <div class="description input">
<label for="name">Name</label>
<input type="text" name="name" placeholder="Your Name">
</div>
<div class="description input">
<label for="email">Email</label>
<input type="email" name="email" placeholder="Your Email">
</div> -->
<!--<div class="description check-ip">
<input type="checkbox" name="ipv6"> Ipv6 Only<br>
</div>-->
</div>
<input type="submit" class="btn btn-primary" value="{% trans 'Order Now!' %}"></input>
</form>
</div>
</div>
<div class="text">
<h2 class="section-heading">{% trans "Simple and affordable: Try our virtual machine with featherlight price." %}</h2>
<div class="description">
<p>{% trans "Our VMs are hosted in Glarus, Switzerland, and our website is currently running in BETA mode. If you want more information that you did not find on our website, or if your order is more detailed, or if you encounter any technical hiccups, please contact us at support@datacenterlight.ch, our team will get in touch with you asap." %}</p>
</div>
</div>
</div>
{% endblock %}

View file

@ -12,6 +12,7 @@ from datacenterlight.models import VMTemplate
from datacenterlight.tasks import create_vm_task from datacenterlight.tasks import create_vm_task
from membership.models import StripeCustomer from membership.models import StripeCustomer
from opennebula_api.serializers import VMTemplateSerializer from opennebula_api.serializers import VMTemplateSerializer
from utils.hosting_utils import get_vm_price
from utils.models import BillingAddress from utils.models import BillingAddress
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
@ -94,12 +95,11 @@ class CeleryTaskTestCase(TestCase):
cpu = specs.get('cpu') cpu = specs.get('cpu')
memory = specs.get('memory') memory = specs.get('memory')
disk_size = specs.get('disk_size') disk_size = specs.get('disk_size')
amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6) amount_to_be_charged = get_vm_price(cpu=cpu, memory=memory,
plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format( disk_size=disk_size)
cpu=cpu, plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
memory=memory, memory=memory,
disk_size=disk_size) disk_size=disk_size)
stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu, stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
ram=memory, ram=memory,
ssd=disk_size, ssd=disk_size,

View file

@ -1,7 +1,7 @@
from django.conf.urls import url from django.conf.urls import url
from .views import IndexView, BetaProgramView, LandingProgramView, \ from .views import IndexView, BetaProgramView, LandingProgramView, \
BetaAccessView, PricingView, SuccessView, \ BetaAccessView, SuccessView, \
PaymentOrderView, OrderConfirmationView, \ PaymentOrderView, OrderConfirmationView, \
WhyDataCenterLightView, ContactUsView WhyDataCenterLightView, ContactUsView
@ -15,7 +15,6 @@ urlpatterns = [
name='whydatacenterlight'), name='whydatacenterlight'),
url(r'^beta-program/?$', BetaProgramView.as_view(), name='beta'), url(r'^beta-program/?$', BetaProgramView.as_view(), name='beta'),
url(r'^landing/?$', LandingProgramView.as_view(), name='landing'), url(r'^landing/?$', LandingProgramView.as_view(), name='landing'),
url(r'^pricing/?$', PricingView.as_view(), name='pricing'),
url(r'^payment/?$', PaymentOrderView.as_view(), name='payment'), url(r'^payment/?$', PaymentOrderView.as_view(), name='payment'),
url(r'^order-confirmation/?$', OrderConfirmationView.as_view(), url(r'^order-confirmation/?$', OrderConfirmationView.as_view(),
name='order_confirmation'), name='order_confirmation'),

View file

@ -1,28 +1,35 @@
import logging
import json
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import login, authenticate
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import redirect
from django.shortcuts import render from django.shortcuts import render
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import get_language, ugettext_lazy as _
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
from django.views.generic import FormView, CreateView, TemplateView, DetailView from django.views.generic import FormView, CreateView, TemplateView, DetailView
from datacenterlight.tasks import create_vm_task from datacenterlight.tasks import create_vm_task
from hosting.models import HostingOrder from hosting.models import HostingOrder
from hosting.forms import HostingUserLoginForm
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import VMTemplateSerializer
from opennebula_api.serializers import VirtualMachineTemplateSerializer, \ from utils.forms import (
VMTemplateSerializer BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm
from utils.forms import BillingAddressForm )
from utils.hosting_utils import get_vm_price
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.tasks import send_plain_email_task from utils.tasks import send_plain_email_task
from .forms import BetaAccessForm, ContactForm from .forms import BetaAccessForm, ContactForm
from .models import BetaAccess, BetaAccessVMType, BetaAccessVM, VMTemplate from .models import BetaAccess, BetaAccessVMType, BetaAccessVM, VMTemplate
logger = logging.getLogger(__name__)
class ContactUsView(FormView): class ContactUsView(FormView):
template_name = "datacenterlight/contact_form.html" template_name = "datacenterlight/contact_form.html"
@ -88,56 +95,6 @@ class SuccessView(TemplateView):
return render(request, self.template_name) return render(request, self.template_name)
class PricingView(TemplateView):
template_name = "datacenterlight/pricing.html"
def get(self, request, *args, **kwargs):
try:
manager = OpenNebulaManager()
templates = manager.get_templates()
context = {
'templates': VirtualMachineTemplateSerializer(templates,
many=True).data,
}
except:
messages.error(request,
'We have a temporary problem to connect to our backend. \
Please try again in a few minutes'
)
context = {
'error': 'connection'
}
return render(request, self.template_name, context)
def post(self, request):
cores = request.POST.get('cpu')
memory = request.POST.get('ram')
storage = request.POST.get('storage')
price = request.POST.get('total')
template_id = int(request.POST.get('config'))
manager = OpenNebulaManager()
template = manager.get_template(template_id)
request.session['template'] = VirtualMachineTemplateSerializer(
template).data
if not request.user.is_authenticated():
request.session['next'] = reverse('hosting:payment')
request.session['specs'] = {
'cpu': cores,
'memory': memory,
'disk_size': storage,
'price': price,
}
return redirect(reverse('hosting:payment'))
class BetaAccessView(FormView): class BetaAccessView(FormView):
template_name = "datacenterlight/beta_access.html" template_name = "datacenterlight/beta_access.html"
form_class = BetaAccessForm form_class = BetaAccessForm
@ -274,17 +231,11 @@ class IndexView(CreateView):
memory_field = forms.IntegerField(validators=[self.validate_memory]) memory_field = forms.IntegerField(validators=[self.validate_memory])
storage = request.POST.get('storage') storage = request.POST.get('storage')
storage_field = forms.IntegerField(validators=[self.validate_storage]) storage_field = forms.IntegerField(validators=[self.validate_storage])
price = request.POST.get('total')
template_id = int(request.POST.get('config')) template_id = int(request.POST.get('config'))
template = VMTemplate.objects.filter( template = VMTemplate.objects.filter(
opennebula_vm_template_id=template_id).first() opennebula_vm_template_id=template_id).first()
template_data = VMTemplateSerializer(template).data template_data = VMTemplateSerializer(template).data
name = request.POST.get('name')
email = request.POST.get('email')
name_field = forms.CharField()
email_field = forms.EmailField()
try: try:
cores = cores_field.clean(cores) cores = cores_field.clean(cores)
except ValidationError as err: except ValidationError as err:
@ -311,40 +262,16 @@ class IndexView(CreateView):
extra_tags='storage') extra_tags='storage')
return HttpResponseRedirect( return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form") reverse('datacenterlight:index') + "#order_form")
amount_to_be_charged = get_vm_price(cpu=cores, memory=memory,
try: disk_size=storage)
name = name_field.clean(name)
except ValidationError as err:
msg = '{} {}.'.format(name, _('is not a proper name'))
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='name')
return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
try:
email = email_field.clean(email)
except ValidationError as err:
msg = '{} {}.'.format(email, _('is not a proper email'))
messages.add_message(self.request, messages.ERROR, msg,
extra_tags='email')
return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form")
specs = { specs = {
'cpu': cores, 'cpu': cores,
'memory': memory, 'memory': memory,
'disk_size': storage, 'disk_size': storage,
'price': price 'price': amount_to_be_charged
} }
this_user = {
'name': name,
'email': email
}
request.session['specs'] = specs request.session['specs'] = specs
request.session['template'] = template_data request.session['template'] = template_data
request.session['user'] = this_user
return HttpResponseRedirect(reverse('datacenterlight:payment')) return HttpResponseRedirect(reverse('datacenterlight:payment'))
def get_success_url(self): def get_success_url(self):
@ -407,20 +334,19 @@ class WhyDataCenterLightView(IndexView):
class PaymentOrderView(FormView): class PaymentOrderView(FormView):
template_name = 'datacenterlight/landing_payment.html' template_name = 'datacenterlight/landing_payment.html'
form_class = BillingAddressForm
def get_form_class(self):
if self.request.user.is_authenticated():
return BillingAddressForm
else:
return BillingAddressFormSignup
def get_form_kwargs(self): def get_form_kwargs(self):
form_kwargs = super(PaymentOrderView, self).get_form_kwargs() form_kwargs = super(PaymentOrderView, self).get_form_kwargs()
billing_address_data = self.request.session.get('billing_address_data') # if user is signed in, get billing address
if billing_address_data: if self.request.user.is_authenticated():
form_kwargs.update({ form_kwargs.update({
'initial': { 'instance': self.request.user.billing_addresses.first()
'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'],
}
}) })
return form_kwargs return form_kwargs
@ -428,48 +354,49 @@ class PaymentOrderView(FormView):
context = super(PaymentOrderView, self).get_context_data(**kwargs) context = super(PaymentOrderView, self).get_context_data(**kwargs)
context.update({ context.update({
'stripe_key': settings.STRIPE_API_PUBLIC_KEY, 'stripe_key': settings.STRIPE_API_PUBLIC_KEY,
'site_url': reverse('datacenterlight:index') 'site_url': reverse('datacenterlight:index'),
'login_form': HostingUserLoginForm()
}) })
return context return context
@cache_control(no_cache=True, must_revalidate=True, no_store=True) @cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if 'specs' not in request.session or 'user' not in request.session: # user is no longer added to session on the index page
if 'specs' not in request.session:
return HttpResponseRedirect(reverse('datacenterlight:index')) return HttpResponseRedirect(reverse('datacenterlight:index'))
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
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
billing_address_data = form.cleaned_data
token = form.cleaned_data.get('token') token = form.cleaned_data.get('token')
user = request.session.get('user') if request.user.is_authenticated():
try: this_user = {
CustomUser.objects.get(email=user.get('email')) 'email': request.user.email,
except CustomUser.DoesNotExist: 'name': request.user.name
password = CustomUser.get_random_password() }
# Register the user, and do not send emails customer = StripeCustomer.get_or_create(
CustomUser.register(user.get('name'), email=this_user.get('email'),
password, token=token)
user.get('email'), else:
app='dcl', this_user = {
base_url=None, send_email=False) 'email': form.cleaned_data.get('email'),
'name': form.cleaned_data.get('name')
}
customer = StripeCustomer.create_stripe_api_customer(
email=this_user.get('email'),
token=token,
customer_name=form.cleaned_data.get('name'))
request.session['billing_address_data'] = form.cleaned_data
request.session['user'] = this_user
# Get or create stripe customer # Get or create stripe customer
customer = StripeCustomer.get_or_create(email=user.get('email'),
token=token)
if not customer: if not customer:
form.add_error("__all__", "Invalid credit card") form.add_error("__all__", "Invalid credit card")
return self.render_to_response( return self.render_to_response(
self.get_context_data(form=form)) self.get_context_data(form=form))
# Create Billing Address
billing_address = form.save()
request.session['billing_address_data'] = billing_address_data
request.session['billing_address'] = billing_address.id
request.session['token'] = token request.session['token'] = token
request.session['customer'] = customer.id request.session[
'customer'] = customer.id if request.user.is_authenticated() else customer
return HttpResponseRedirect( return HttpResponseRedirect(
reverse('datacenterlight:order_confirmation')) reverse('datacenterlight:order_confirmation'))
else: else:
@ -489,9 +416,15 @@ class OrderConfirmationView(DetailView):
if 'token' not in request.session: if 'token' not in request.session:
return HttpResponseRedirect(reverse('datacenterlight:payment')) return HttpResponseRedirect(reverse('datacenterlight:payment'))
stripe_customer_id = request.session.get('customer') stripe_customer_id = request.session.get('customer')
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() if request.user.is_authenticated():
customer = StripeCustomer.objects.filter(
id=stripe_customer_id).first()
stripe_api_cus_id = customer.stripe_id
else:
stripe_api_cus_id = stripe_customer_id
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
card_details = stripe_utils.get_card_details(customer.stripe_id, card_details = stripe_utils.get_card_details(stripe_api_cus_id,
request.session.get( request.session.get(
'token')) 'token'))
if not card_details.get('response_object'): if not card_details.get('response_object'):
@ -503,7 +436,8 @@ class OrderConfirmationView(DetailView):
context = { context = {
'site_url': reverse('datacenterlight:index'), 'site_url': reverse('datacenterlight:index'),
'cc_last4': card_details.get('response_object').get('last4'), 'cc_last4': card_details.get('response_object').get('last4'),
'cc_brand': card_details.get('response_object').get('brand') 'cc_brand': card_details.get('response_object').get('brand'),
'billing_address_data': request.session.get('billing_address_data')
} }
return render(request, self.template_name, context) return render(request, self.template_name, context)
@ -512,32 +446,43 @@ class OrderConfirmationView(DetailView):
specs = request.session.get('specs') specs = request.session.get('specs')
user = request.session.get('user') user = request.session.get('user')
stripe_customer_id = request.session.get('customer') stripe_customer_id = request.session.get('customer')
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() if request.user.is_authenticated():
billing_address_data = request.session.get('billing_address_data') customer = StripeCustomer.objects.filter(
billing_address_id = request.session.get('billing_address') id=stripe_customer_id).first()
stripe_api_cus_id = customer.stripe_id
else:
stripe_api_cus_id = stripe_customer_id
vm_template_id = template.get('id', 1) vm_template_id = template.get('id', 1)
# Make stripe charge to a customer
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
card_details = stripe_utils.get_card_details(customer.stripe_id, card_details = stripe_utils.get_card_details(stripe_api_cus_id,
request.session.get( request.session.get(
'token')) 'token'))
if not card_details.get('response_object'): if not card_details.get('response_object'):
msg = card_details.get('error') msg = card_details.get('error')
messages.add_message(self.request, messages.ERROR, msg, messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment') extra_tags='failed_payment')
return HttpResponseRedirect( response = {
reverse('datacenterlight:payment') + '#payment_error') 'status': False,
'redirect': "{url}#{section}".format(
url=reverse('datacenterlight:payment'),
section='payment_error'),
'msg_title': str(_('Error.')),
'msg_body': str(
_('There was a payment related error.'
' On close of this popup, you will be redirected back to'
' the payment page.'))
}
return HttpResponse(json.dumps(response),
content_type="application/json")
card_details_dict = card_details.get('response_object') card_details_dict = card_details.get('response_object')
cpu = specs.get('cpu') cpu = specs.get('cpu')
memory = specs.get('memory') memory = specs.get('memory')
disk_size = specs.get('disk_size') disk_size = specs.get('disk_size')
amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6) amount_to_be_charged = specs.get('price')
plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format( plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
cpu=cpu, memory=memory,
memory=memory, disk_size=disk_size)
disk_size=disk_size)
stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu, stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
ram=memory, ram=memory,
ssd=disk_size, ssd=disk_size,
@ -548,7 +493,7 @@ class OrderConfirmationView(DetailView):
name=plan_name, name=plan_name,
stripe_plan_id=stripe_plan_id) stripe_plan_id=stripe_plan_id)
subscription_result = stripe_utils.subscribe_customer_to_plan( subscription_result = stripe_utils.subscribe_customer_to_plan(
customer.stripe_id, stripe_api_cus_id,
[{"plan": stripe_plan.get( [{"plan": stripe_plan.get(
'response_object').stripe_plan_id}]) 'response_object').stripe_plan_id}])
stripe_subscription_obj = subscription_result.get('response_object') stripe_subscription_obj = subscription_result.get('response_object')
@ -558,11 +503,96 @@ class OrderConfirmationView(DetailView):
msg = subscription_result.get('error') msg = subscription_result.get('error')
messages.add_message(self.request, messages.ERROR, msg, messages.add_message(self.request, messages.ERROR, msg,
extra_tags='failed_payment') extra_tags='failed_payment')
return HttpResponseRedirect( response = {
reverse('datacenterlight:payment') + '#payment_error') 'status': False,
'redirect': "{url}#{section}".format(
url=reverse('datacenterlight:payment'),
section='payment_error'),
'msg_title': str(_('Error.')),
'msg_body': str(
_('There was a payment related error.'
' On close of this popup, you will be redirected back to'
' the payment page.'))
}
return HttpResponse(json.dumps(response),
content_type="application/json")
# Create user if the user is not logged in and if he is not already
# registered
if not request.user.is_authenticated():
try:
custom_user = CustomUser.objects.get(
email=user.get('email'))
customer = StripeCustomer.objects.filter(
user_id=custom_user.id).first()
stripe_customer_id = customer.id
except CustomUser.DoesNotExist:
logger.debug(
"Customer {} does not exist.".format(user.get('email')))
password = CustomUser.get_random_password()
base_url = "{0}://{1}".format(self.request.scheme,
self.request.get_host())
custom_user = CustomUser.register(
user.get('name'), password,
user.get('email'),
app='dcl', base_url=base_url, send_email=True,
account_details=password
)
logger.debug("Created user {}.".format(user.get('email')))
stripe_customer = StripeCustomer.objects. \
create(user=custom_user, stripe_id=stripe_api_cus_id)
stripe_customer_id = stripe_customer.id
new_user = authenticate(username=custom_user.email,
password=password)
login(request, new_user)
else:
customer = StripeCustomer.objects.filter(
id=stripe_customer_id).first()
custom_user = customer.user
stripe_customer_id = customer.id
# Save billing address
billing_address_data = request.session.get('billing_address_data')
logger.debug('billing_address_data is {}'.format(billing_address_data))
billing_address_data.update({
'user': custom_user.id
})
billing_address_user_form = UserBillingAddressForm(
instance=custom_user.billing_addresses.first(),
data=billing_address_data)
billing_address = billing_address_user_form.save()
billing_address_id = billing_address.id
logger.debug("billing address id = {}".format(billing_address_id))
user = {
'name': custom_user.name,
'email': custom_user.email,
'pass': custom_user.password,
'request_scheme': request.scheme,
'request_host': request.get_host(),
'language': get_language(),
}
create_vm_task.delay(vm_template_id, user, specs, template, create_vm_task.delay(vm_template_id, user, specs, template,
stripe_customer_id, billing_address_data, stripe_customer_id, billing_address_data,
billing_address_id, billing_address_id,
stripe_subscription_obj, card_details_dict) stripe_subscription_obj, card_details_dict)
request.session['order_confirmation'] = True for session_var in ['specs', 'template', 'billing_address',
return HttpResponseRedirect(reverse('datacenterlight:order_success')) 'billing_address_data',
'token', 'customer']:
if session_var in request.session:
del request.session[session_var]
response = {
'status': True,
'redirect': reverse(
'hosting:virtual_machines') if request.user.is_authenticated() else reverse(
'datacenterlight:index'),
'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")

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-23 19:00+0530\n" "POT-Creation-Date: 2017-09-24 12:34+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"
@ -268,7 +268,7 @@ msgstr ""
"Link klickst</a>.<br/>\n" "Link klickst</a>.<br/>\n"
msgid "My VM page" msgid "My VM page"
msgstr "" msgstr "Meine VM page"
#, python-format #, python-format
msgid "" msgid ""
@ -306,6 +306,9 @@ msgstr "Registrieren"
msgid "Forgot your password ? " msgid "Forgot your password ? "
msgstr "Passwort vergessen?" msgstr "Passwort vergessen?"
msgid "Resend activation link"
msgstr "Aktivierungslink noch einmal senden"
msgid "Notifications" msgid "Notifications"
msgstr "Benachrichtigungen" msgstr "Benachrichtigungen"
@ -385,7 +388,7 @@ msgid "Processing..."
msgstr "Abarbeitung..." msgstr "Abarbeitung..."
msgid "Hold tight, we are processing your request" msgid "Hold tight, we are processing your request"
msgstr "Bitte warten - wir verbeiten Deine Anfrage gerade" msgstr "Bitte warten - wir bearbeiten Deine Anfrage gerade"
msgid "Some problem encountered. Please try again later." msgid "Some problem encountered. Please try again later."
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal." msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
@ -620,7 +623,7 @@ msgid "View Detail"
msgstr "Details anzeigen" msgstr "Details anzeigen"
msgid "login" msgid "login"
msgstr "Einloggen" msgstr "anmelden"
msgid "" msgid ""
"Thank you for signing up. We have sent an email to you. Please follow the " "Thank you for signing up. We have sent an email to you. Please follow the "

View file

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2017-09-24 18:12
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('hosting', '0042_hostingorder_subscription_id'),
]
operations = [
migrations.CreateModel(
name='VMDetail',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vm_id', models.IntegerField(default=0)),
('disk_size', models.FloatField(default=0.0)),
('cores', models.FloatField(default=0.0)),
('memory', models.FloatField(default=0.0)),
('configuration', models.CharField(default='', max_length=25)),
('ipv4', models.TextField(default='')),
('ipv6', models.TextField(default='')),
('created_at', models.DateTimeField(auto_now_add=True)),
('terminated_at', models.DateTimeField(null=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View file

@ -159,3 +159,16 @@ class HostingBill(AssignPermissionsMixin, models.Model):
instance = cls.objects.create(customer=customer, instance = cls.objects.create(customer=customer,
billing_address=billing_address) billing_address=billing_address)
return instance return instance
class VMDetail(models.Model):
user = models.ForeignKey(CustomUser)
vm_id = models.IntegerField(default=0)
disk_size = models.FloatField(default=0.0)
cores = models.FloatField(default=0.0)
memory = models.FloatField(default=0.0)
configuration = models.CharField(default='', max_length=25)
ipv4 = models.TextField(default='')
ipv6 = models.TextField(default='')
created_at = models.DateTimeField(auto_now_add=True)
terminated_at = models.DateTimeField(null=True)

View file

@ -857,6 +857,8 @@ a.list-group-item-danger:focus,
background: rgba(235, 204, 209, 0.2); background: rgba(235, 204, 209, 0.2);
} }
.has-error .form-control, .has-error .form-control,
.has-error .form-control:focus,
.has-error .form-control:active,
.has-error .input-group-addon { .has-error .input-group-addon {
color: #eb4d5c; color: #eb4d5c;
border-color: #eb4d5c; border-color: #eb4d5c;
@ -870,3 +872,48 @@ a.list-group-item-danger.active:focus {
.panel-danger > .panel-heading .badge { .panel-danger > .panel-heading .badge {
background-color: #eb4d5c; background-color: #eb4d5c;
} }
/* bootstrap input box-shadom disable */
.has-error .form-control:focus,
.has-error .form-control:active,
.has-success .form-control:focus,
.has-success .form-control:active {
box-shadow: inset 0 0 1px rgba(0,0,0,0.25);
}
.checkmark {
display: inline-block;
}
.checkmark:after {
/*Add another block-level blank space*/
content: '';
display: block;
/*Make it a small rectangle so the border will create an L-shape*/
width: 25px;
height: 60px;
/*Add a white border on the bottom and left, creating that 'L' */
border: solid #777;
border-width: 0 3px 3px 0;
/*Rotate the L 45 degrees to turn it into a checkmark*/
transform: rotate(45deg);
}
.closemark {
display: inline-block;
width: 50px;
height: 50px;
position: relative;
}
.closemark:before, .closemark:after {
position: absolute;
left: 25px;
content: ' ';
height: 50px;
width: 2px;
background-color: #777;
}
.closemark:before {
transform: rotate(45deg);
}
.closemark:after {
transform: rotate(-45deg);
}

View file

@ -22,7 +22,100 @@
} }
.summary-box .content { .summary-box .content {
padding-top: 15px; padding-top: 15px;
}
/* landing page payment new style */
.last-p {
margin-bottom: 0;
}
.dcl-payment-section {
max-width: 391px;
margin: 0 auto 30px;
padding: 0 10px 30px;
border-bottom: 1px solid #edebeb;
height: 100%;
}
.dcl-payment-section hr{
margin-top: 15px;
margin-bottom: 15px;
}
.dcl-payment-section .top-hr {
margin-left: -10px;
}
.dcl-payment-section h3 {
font-weight: 600;
}
.dcl-payment-section p {
padding: 0 5px;
font-weight: 400;
}
.dcl-payment-section .card-warning-content {
padding: 8px 10px;
font-weight: 300;
}
.dcl-payment-order strong{
font-size: 17px;
}
.dcl-payment-order p {
font-weight: 300;
}
.dcl-payment-section .form-group {
margin-bottom: 10px;
}
.dcl-payment-section .form-control {
box-shadow: none;
padding: 6px 12px;
height: 32px;
}
.dcl-payment-user {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.dcl-payment-user h4 {
font-weight: 600;
padding-left: 5px;
font-size: 17px;
}
@media (min-width: 768px) {
.dcl-payment-grid {
display: flex;
align-items: stretch;
flex-wrap: wrap;
}
.dcl-payment-box {
width: 50%;
position: relative;
padding: 0 30px;
}
.dcl-payment-box:nth-child(2) {
order: 1;
}
.dcl-payment-box:nth-child(4) {
order: 2;
}
.dcl-payment-section {
padding: 15px 10px;
margin-bottom: 0;
border-bottom-width: 5px;
}
.dcl-payment-box:nth-child(2n) .dcl-payment-section {
border-bottom: none;
}
.dcl-payment-box:nth-child(1):after,
.dcl-payment-box:nth-child(2):after {
content: ' ';
display: block;
background: #eee;
width: 1px;
position: absolute;
right: 0;
z-index: 2;
top: 20px;
bottom: 20px;
}
} }

View file

@ -79,7 +79,6 @@ $(document).ready(function() {
$('html,body').scrollTop(scrollmem); $('html,body').scrollTop(scrollmem);
}); });
$('.modal-text').removeClass('hide');
var create_vm_form = $('#virtual_machine_create_form'); var create_vm_form = $('#virtual_machine_create_form');
create_vm_form.submit(function () { create_vm_form.submit(function () {
$('#btn-create-vm').prop('disabled', true); $('#btn-create-vm').prop('disabled', true);
@ -90,26 +89,28 @@ $(document).ready(function() {
success: function (data) { success: function (data) {
if (data.status === true) { if (data.status === true) {
fa_icon = $('.modal-icon > .fa'); fa_icon = $('.modal-icon > .fa');
fa_icon.attr('class', 'fa fa-check'); fa_icon.attr('class', 'checkmark');
$('.modal-header > .close').attr('class', 'close'); // $('.modal-header > .close').removeClass('hidden');
$('#createvm-modal-title').text(data.msg_title); $('#createvm-modal-title').text(data.msg_title);
$('#createvm-modal-body').text(data.msg_body); $('#createvm-modal-body').text(data.msg_body);
$('#createvm-modal').on('hidden.bs.modal', function () { $('#createvm-modal-done-btn')
window.location = data.redirect; .attr('href', data.redirect)
}) .removeClass('hide');
} }
}, },
error: function (xmlhttprequest, textstatus, message) { error: function (xmlhttprequest, textstatus, message) {
fa_icon = $('.modal-icon > .fa'); fa_icon = $('.modal-icon > .fa');
fa_icon.attr('class', 'fa fa-times'); fa_icon.attr('class', 'fa fa-close');
$('.modal-header > .close').attr('class', 'close');
$('.modal-text').addClass('hide');
if (typeof(create_vm_error_message) !== 'undefined') { if (typeof(create_vm_error_message) !== 'undefined') {
$('#createvm-modal-title').text(create_vm_error_message); $('#createvm-modal-text').text(create_vm_error_message);
} }
$('#btn-create-vm').prop('disabled', false); $('#btn-create-vm').prop('disabled', false);
$('#createvm-modal-close-btn').removeClass('hide');
} }
}); });
return false; return false;
}); });
$('#createvm-modal').on('hidden.bs.modal', function () {
$(this).find('.modal-footer .btn').addClass('hide');
})
}); });

View file

@ -47,20 +47,5 @@
window.location.href = '{{next_url}}'; window.location.href = '{{next_url}}';
</script> </script>
{% endif %} {% endif %}
<script type="text/javascript">
window.onload = function () {
{% for user_key in keys %}
var locale_date = moment.utc(document.getElementById("ssh-created_at-{{user_key.id}}").textContent,'YYYY-MM-DD HH:mm').toDate();
locale_date = moment(locale_date).format("YYYY-MM-DD h:mm:ss a");
document.getElementById('ssh-created_at-{{user_key.id}}').innerHTML = locale_date;
{% endfor %}
};
</script>
{%endblock%} {%endblock%}

View file

@ -44,6 +44,8 @@
<a class="unlink" href="{% url 'hosting:signup' %}">{% trans "Sign up"%}</a> <a class="unlink" href="{% url 'hosting:signup' %}">{% trans "Sign up"%}</a>
<span class="text"> or </span> <span class="text"> or </span>
<a class="unlink" href="{% url 'hosting:reset_password' %}">{% trans "Forgot your password ? "%}</a> <a class="unlink" href="{% url 'hosting:reset_password' %}">{% trans "Forgot your password ? "%}</a>
<span class="text"> or </span><br/>
<a class="unlink" href="{% url 'hosting:resend_activation_link' %}">{% trans "Resend activation link"%}</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -42,7 +42,9 @@
<p> <p>
<strong>{% trans "Status" %}: </strong> <strong>{% trans "Status" %}: </strong>
<strong> <strong>
{% if order.status == 'Approved' %} {% if vm.terminated_at %}
<span class="vm-color-failed">{% trans "Terminated" %}</span>
{% elif order.status == 'Approved' %}
<span class="vm-color-online">{% trans "Approved" %}</span> <span class="vm-color-online">{% trans "Approved" %}</span>
{% else %} {% else %}
<span class="vm-status-failed">{% trans "Declined" %}</span> <span class="vm-status-failed">{% trans "Declined" %}</span>
@ -96,14 +98,14 @@
</p> </p>
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-6">
{% comment %} {% if vm.created_at %}
<p>
<span>{% trans "Period" %}: </span>
<span>{{ vm.created_at|date:'Y/m/d' }} - {% if vm.terminated_at %}{{ vm.terminated_at|date:'Y/m/d' }}{% else %}{% now 'Y/m/d' %}{% endif %}</span>
</p>
{% endif %}
<p> <p>
<span>{% trans "Period" %}</span> <span>{% trans "Cores" %}: </span>
<span class="pull-right">{{}}</span>
</p>
{% endcomment %}
<p>
<span>{% trans "Cores" %}</span>
{% if vm.cores %} {% if vm.cores %}
<span class="pull-right">{{vm.cores|floatformat}}</span> <span class="pull-right">{{vm.cores|floatformat}}</span>
{% else %} {% else %}
@ -111,11 +113,11 @@
{% endif %} {% endif %}
</p> </p>
<p> <p>
<span>{% trans "Memory" %}</span> <span>{% trans "Memory" %}: </span>
<span class="pull-right">{{vm.memory}} GB</span> <span class="pull-right">{{vm.memory}} GB</span>
</p> </p>
<p> <p>
<span>{% trans "Disk space" %}</span> <span>{% trans "Disk space" %}: </span>
<span class="pull-right">{{vm.disk_size}} GB</span> <span class="pull-right">{{vm.disk_size}} GB</span>
</p> </p>
<p> <p>
@ -160,22 +162,19 @@
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <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>
<div class="modal-body"> <div class="modal-body">
<div class="modal-icon"> <div class="modal-icon">
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i> <i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
<span class="sr-only">{% trans "Processing..." %}</span> <span class="sr-only">{% trans "Processing..." %}</span>
</div> </div>
<h4 class="modal-title" id="createvm-modal-title"> <h4 class="modal-title" id="createvm-modal-title"></h4>
</h4>
<div class="modal-text" id="createvm-modal-body"> <div class="modal-text" id="createvm-modal-body">
{% trans "Hold tight, we are processing your request" %} {% trans "Hold tight, we are processing your request" %}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a id="createvm-modal-done-btn" class="btn btn-success btn-ok btn-wide hide" href="{% url 'hosting:virtual_machines' %}">{% trans "OK" %}</a>
<button id="createvm-modal-close-btn" type="button" class="btn btn-danger btn-ok btn-wide hide" data-dismiss="modal" aria-label="create-vm-close">{% trans "Close" %}</button>
</div> </div>
</div> </div>
</div> </div>
@ -202,4 +201,4 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script>
<script src="{% static 'hosting/js/html2pdf.js' %}"></script> <script src="{% static 'hosting/js/html2pdf.js' %}"></script>
<script src="{% static 'hosting/js/order.js' %}"></script> <script src="{% static 'hosting/js/order.js' %}"></script>
{% endblock js_extra %} {% endblock js_extra %}

View file

@ -0,0 +1,36 @@
{% extends "hosting/base_short.html" %}
{% load staticfiles bootstrap3%}
{% load i18n %}
{% block navbar %}
{% include 'hosting/includes/_navbar_transparent.html' %}
{% endblock navbar %}
{% block content %}
<div class="auth-container">
<div class="auth-bg"></div>
<div class="auth-center">
<div class="auth-title">
<h2>{% trans "Your VM hosted in Switzerland"%}</h2>
</div>
<div class="auth-content">
<div class="intro-message auth-box sign-up">
<h2 class="section-heading">{% trans "Resend activation link"%}</h2>
<form action="{% url 'hosting:resend_activation_link' %}" method="post" class="form" novalidate>
{% csrf_token %}
{% for field in form %}
{% bootstrap_field field show_label=False %}
{% endfor %}
{% buttons %}
<button type="submit" class="btn btn-block btn-success">
{% trans "Submit"%}
</button>
{% endbuttons %}
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -101,21 +101,5 @@
window.location.href = '{{next_url}}'; window.location.href = '{{next_url}}';
</script> </script>
{% endif %} {% endif %}
<script type="text/javascript">
window.onload = function () {
{% for user_key in keys %}
var locale_date = moment.utc(document.getElementById("ssh-created_at-{{user_key.id}}").textContent,'YYYY-MM-DD HH:mm').toDate();
locale_date = moment(locale_date).format("YYYY-MM-DD h:mm:ss a");
document.getElementById('ssh-created_at-{{user_key.id}}').innerHTML = locale_date;
{% endfor %}
};
</script>
{%endblock%} {%endblock%}

View file

@ -109,7 +109,7 @@
<p><strong>{{virtual_machine.name}}</strong></p> <p><strong>{{virtual_machine.name}}</strong></p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a class="btn btn-danger btn-ok btn-wide">{% trans "OK" %}</a> <a class="btn btn-danger btn-ok btn-wide">{% trans "OK" %}</a>
</div> </div>
</div> </div>
</div> </div>
@ -123,8 +123,9 @@
<div class="modal-header"> <div class="modal-header">
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="modal-icon"><i class="fa fa-check" aria-hidden="true"></i></div> <div class="modal-icon"><i class="checkmark" aria-hidden="true"></i></div>
<h4 class="modal-title" id="ModalLabel">{% blocktrans with machine_name=virtual_machine.name %}Your Virtual Machine <strong>{{machine_name}}</strong> is successfully terminated!{% endblocktrans %}</h4> <h4 class="modal-title"></h4>
<div class="modal-text" id="ModalLabel">{% blocktrans with machine_name=virtual_machine.name %}Your Virtual Machine <strong>{{machine_name}}</strong> is successfully terminated!{% endblocktrans %}</div>
<div class="modal-footer"> <div class="modal-footer">
<a href="{% url 'hosting:virtual_machines' %}" class="btn btn-success btn-wide">{% trans "OK" %}</a> <a href="{% url 'hosting:virtual_machines' %}" class="btn btn-success btn-wide">{% trans "OK" %}</a>
</div> </div>

View file

@ -13,7 +13,12 @@
{% endif %} {% endif %}
{% if not error %} {% if not error %}
<div class="dashboard-subtitle"> <div class="dashboard-subtitle">
<p>{% trans 'To create a new virtual machine, click "Create VM"' %}</p> <p>{% trans 'To create a new virtual machine, click "Create VM"' %}
{% if show_create_ssh_key_msg %}
{% url 'hosting:create_ssh_key' as create_ssh_url %}
<br/>{% blocktrans %}To access your VM, add your SSH key <a href="{{create_ssh_url}}">here</a>{% endblocktrans %}
{% endif %}
</p>
<div class="text-right"> <div class="text-right">
<a class="btn btn-vm" href="{% url 'hosting:create_virtual_machine' %}"><span class="css-plus"></span> <span>{% trans "CREATE VM" %}</span></a> <a class="btn btn-vm" href="{% url 'hosting:create_virtual_machine' %}"><span class="css-plus"></span> <span>{% trans "CREATE VM" %}</span></a>
</div> </div>

View file

@ -8,8 +8,7 @@ from .views import (
MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView,
HostingPricingView, CreateVirtualMachinesView, HostingBillListView, HostingPricingView, CreateVirtualMachinesView, HostingBillListView,
HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView, HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView,
SSHKeyChoiceView, DashboardView, SettingsView) SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView)
urlpatterns = [ urlpatterns = [
url(r'index/?$', IndexView.as_view(), name='index'), url(r'index/?$', IndexView.as_view(), name='index'),
@ -52,6 +51,8 @@ urlpatterns = [
url(r'signup/?$', SignupView.as_view(), name='signup'), url(r'signup/?$', SignupView.as_view(), name='signup'),
url(r'signup-validate/?$', SignupValidateView.as_view(), url(r'signup-validate/?$', SignupValidateView.as_view(),
name='signup-validate'), name='signup-validate'),
url(r'resend-activation-link/?$', ResendActivationEmailView.as_view(),
name='resend_activation_link'),
url(r'reset-password/?$', PasswordResetView.as_view(), url(r'reset-password/?$', PasswordResetView.as_view(),
name='reset_password'), 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>.+)/$',

View file

@ -1,6 +1,7 @@
import json import json
import logging import logging
import uuid import uuid
from datetime import datetime
from time import sleep from time import sleep
from django import forms from django import forms
@ -33,20 +34,25 @@ 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, VMTemplateSerializer VirtualMachineTemplateSerializer, VMTemplateSerializer
from utils.forms import BillingAddressForm, PasswordResetRequestForm, \ from utils.forms import (
UserBillingAddressForm BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm,
ResendActivationEmailForm
)
from utils.hosting_utils import get_vm_price
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 ( from utils.views import (
PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin,
ResendActivationLinkViewMixin
) )
from .forms import HostingUserSignupForm, HostingUserLoginForm, \ from .forms import HostingUserSignupForm, HostingUserLoginForm, \
UserHostingKeyForm, generate_ssh_key_name UserHostingKeyForm, generate_ssh_key_name
from .mixins import ProcessVMSelectionMixin from .mixins import ProcessVMSelectionMixin
from .models import HostingOrder, HostingBill, HostingPlan, UserHostingKey from .models import (
HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail
)
from datacenterlight.models import VMTemplate from datacenterlight.models import VMTemplate
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \ CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \
@ -282,6 +288,14 @@ class SignupValidatedView(SignupValidateView):
return context return context
class ResendActivationEmailView(ResendActivationLinkViewMixin):
template_name = 'hosting/resend_activation_link.html'
form_class = ResendActivationEmailForm
success_url = reverse_lazy('hosting:login')
email_template_path = 'datacenterlight/emails/'
email_template_name = 'user_activation'
class PasswordResetView(PasswordResetViewMixin): class PasswordResetView(PasswordResetViewMixin):
site = 'dcl' site = 'dcl'
template_name = 'hosting/reset_password.html' template_name = 'hosting/reset_password.html'
@ -599,13 +613,6 @@ class PaymentVMView(LoginRequiredMixin, FormView):
return context return context
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if not UserHostingKey.objects.filter(user=self.request.user).exists():
messages.success(
request,
'In order to create a VM, you create/upload your SSH KEY first.'
)
return HttpResponseRedirect(reverse('hosting:ssh_keys'))
if 'next' in request.session: if 'next' in request.session:
del request.session['next'] del request.session['next']
@ -678,25 +685,30 @@ class OrdersHostingDetailView(LoginRequiredMixin,
if obj is not None: if obj is not None:
# invoice for previous order # invoice for previous order
try: try:
manager = OpenNebulaManager( vm_detail = VMDetail.objects.get(vm_id=obj.vm_id)
email=owner.email, password=owner.password context['vm'] = vm_detail.__dict__
) context['vm']['name'] = '{}-{}'.format(context['vm']['configuration'], context['vm']['vm_id'])
vm = manager.get_vm(obj.vm_id) except VMDetail.DoesNotExist:
context['vm'] = VirtualMachineSerializer(vm).data try:
except WrongIdError: manager = OpenNebulaManager(
messages.error( email=owner.email, password=owner.password
self.request, )
_('The VM you are looking for is unavailable at the ' vm = manager.get_vm(obj.vm_id)
'moment. Please contact Data Center Light support.') context['vm'] = VirtualMachineSerializer(vm).data
) except WrongIdError:
self.kwargs['error'] = 'WrongIdError' messages.error(
context['error'] = 'WrongIdError' self.request,
except ConnectionRefusedError: _('The VM you are looking for is unavailable at the '
messages.error( 'moment. Please contact Data Center Light support.')
self.request, )
_('In order to create a VM, you need to create/upload ' self.kwargs['error'] = 'WrongIdError'
'your SSH KEY first.') context['error'] = 'WrongIdError'
) except ConnectionRefusedError:
messages.error(
self.request,
_('In order to create a VM, you need to create/upload '
'your SSH KEY first.')
)
elif not card_details.get('response_object'): elif not card_details.get('response_object'):
# new order, failed to get card details # new order, failed to get card details
context['failed_payment'] = True context['failed_payment'] = True
@ -756,12 +768,11 @@ class OrdersHostingDetailView(LoginRequiredMixin,
cpu = specs.get('cpu') cpu = specs.get('cpu')
memory = specs.get('memory') memory = specs.get('memory')
disk_size = specs.get('disk_size') disk_size = specs.get('disk_size')
amount_to_be_charged = (cpu * 5) + (memory * 2) + (disk_size * 0.6) amount_to_be_charged = get_vm_price(cpu=cpu, memory=memory,
plan_name = "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format( disk_size=disk_size)
cpu=cpu, plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
memory=memory, memory=memory,
disk_size=disk_size) disk_size=disk_size)
stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu, stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
ram=memory, ram=memory,
ssd=disk_size, ssd=disk_size,
@ -806,9 +817,10 @@ class OrdersHostingDetailView(LoginRequiredMixin,
'status': True, 'status': True,
'redirect': reverse('hosting:virtual_machines'), 'redirect': reverse('hosting:virtual_machines'),
'msg_title': str(_('Thank you for the order.')), 'msg_title': str(_('Thank you for the order.')),
'msg_body': str(_('Your VM will be up and running in a few moments.' 'msg_body': str(
' We will send you a confirmation email as soon as' _('Your VM will be up and running in a few moments.'
' it is ready.')) ' We will send you a confirmation email as soon as'
' it is ready.'))
} }
return HttpResponse(json.dumps(response), return HttpResponse(json.dumps(response),
@ -866,6 +878,10 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
context = {'error': 'connection'} context = {'error': 'connection'}
else: else:
context = super(ListView, self).get_context_data(**kwargs) context = super(ListView, self).get_context_data(**kwargs)
if UserHostingKey.objects.filter(user=self.request.user).exists():
context['show_create_ssh_key_msg'] = False
else:
context['show_create_ssh_key_msg'] = True
return context return context
@ -886,15 +902,6 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
raise ValidationError(_('Invalid storage size')) raise ValidationError(_('Invalid storage size'))
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if not UserHostingKey.objects.filter(user=self.request.user).exists():
messages.success(
request,
_(
'In order to create a VM, you need to '
'create/upload your SSH KEY first.'
)
)
return HttpResponseRedirect(reverse('hosting:ssh_keys'))
context = {'templates': VMTemplate.objects.all()} context = {'templates': VMTemplate.objects.all()}
return render(request, self.template_name, context) return render(request, self.template_name, context)
@ -905,7 +912,6 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
memory_field = forms.IntegerField(validators=[self.validate_memory]) memory_field = forms.IntegerField(validators=[self.validate_memory])
storage = request.POST.get('storage') storage = request.POST.get('storage')
storage_field = forms.IntegerField(validators=[self.validate_storage]) storage_field = forms.IntegerField(validators=[self.validate_storage])
price = request.POST.get('total')
template_id = int(request.POST.get('config')) template_id = int(request.POST.get('config'))
template = VMTemplate.objects.filter( template = VMTemplate.objects.filter(
opennebula_vm_template_id=template_id).first() opennebula_vm_template_id=template_id).first()
@ -937,7 +943,8 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
extra_tags='storage') extra_tags='storage')
return HttpResponseRedirect( return HttpResponseRedirect(
reverse('datacenterlight:index') + "#order_form") reverse('datacenterlight:index') + "#order_form")
price = get_vm_price(cpu=cores, memory=memory,
disk_size=storage)
specs = { specs = {
'cpu': cores, 'cpu': cores,
'memory': memory, 'memory': memory,
@ -1043,6 +1050,10 @@ class VirtualMachineView(LoginRequiredMixin, View):
except WrongIdError: except WrongIdError:
response['status'] = True response['status'] = True
response['text'] = ugettext('Terminated') response['text'] = ugettext('Terminated')
vm_detail_obj = VMDetail.objects.filter(
vm_id=opennebula_vm_id).first()
vm_detail_obj.terminated_at = datetime.utcnow()
vm_detail_obj.save()
break break
except BaseException: except BaseException:
break break

View file

@ -82,7 +82,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
@classmethod @classmethod
def register(cls, name, password, email, app='digital_glarus', def register(cls, name, password, email, app='digital_glarus',
base_url=None, send_email=True): base_url=None, send_email=True, account_details=None):
user = cls.objects.filter(email=email).first() user = cls.objects.filter(email=email).first()
if not user: if not user:
user = cls.objects.create_user(name=name, email=email, user = cls.objects.create_user(name=name, email=email,
@ -112,6 +112,9 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
'template_name': 'user_activation', 'template_name': 'user_activation',
'template_path': 'datacenterlight/emails/' 'template_path': 'datacenterlight/emails/'
} }
if account_details:
email_data['context'][
'account_details'] = account_details
email = BaseEmail(**email_data) email = BaseEmail(**email_data)
email.send() email.send()
return user return user
@ -176,6 +179,25 @@ class StripeCustomer(models.Model):
def __str__(self): def __str__(self):
return "%s - %s" % (self.stripe_id, self.user.email) return "%s - %s" % (self.stripe_id, self.user.email)
@classmethod
def create_stripe_api_customer(cls, email=None, token=None,
customer_name=None):
"""
This method creates a Stripe API customer with the given
email, token and customer_name. This is different from
get_or_create method below in that it does not create a
CustomUser and associate the customer created in stripe
with it, while get_or_create does that before creating the
stripe user.
"""
stripe_utils = StripeUtils()
stripe_data = stripe_utils.create_customer(token, email, customer_name)
if stripe_data.get('response_object'):
stripe_cus_id = stripe_data.get('response_object').get('id')
return stripe_cus_id
else:
return None
@classmethod @classmethod
def get_or_create(cls, email=None, token=None): def get_or_create(cls, email=None, token=None):
""" """
@ -195,7 +217,6 @@ class StripeCustomer(models.Model):
except StripeCustomer.DoesNotExist: except StripeCustomer.DoesNotExist:
user = CustomUser.objects.get(email=email) user = CustomUser.objects.get(email=email)
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
stripe_data = stripe_utils.create_customer(token, email, user.name) stripe_data = stripe_utils.create_customer(token, email, user.name)
if stripe_data.get('response_object'): if stripe_data.get('response_object'):

View file

@ -150,9 +150,10 @@ class OpenNebulaManager():
oca.User.METHODS['allocate'], email, oca.User.METHODS['allocate'], email,
password, 'core') password, 'core')
logger.debug( logger.debug(
"User {0} does not exist. Created the user. User id = {1}", "User {} does not exist. Created the user. User id = {}".format(
email, email,
opennebula_user opennebula_user
)
) )
return opennebula_user return opennebula_user
except ConnectionRefusedError: except ConnectionRefusedError:

View file

@ -96,5 +96,4 @@ pyflakes==1.5.0
billiard==3.5.0.3 billiard==3.5.0.3
amqp==2.2.1 amqp==2.2.1
vine==1.1.4 vine==1.1.4
#git+https://github.com/ungleich/cdist.git#egg=cdist cdist==4.7.0
file:///home/app/cdist#egg=cdist

View file

@ -18,7 +18,8 @@ class SignupFormMixin(forms.ModelForm):
model = CustomUser model = CustomUser
fields = ['name', 'email', 'password'] fields = ['name', 'email', 'password']
widgets = { widgets = {
'name': forms.TextInput(attrs={'placeholder': _('Enter your name or company name')}), 'name': forms.TextInput(
attrs={'placeholder': _('Enter your name or company name')}),
} }
def clean_confirm_password(self): def clean_confirm_password(self):
@ -42,7 +43,7 @@ class LoginFormMixin(forms.Form):
is_auth = authenticate(email=email, password=password) is_auth = authenticate(email=email, password=password)
if not is_auth: if not is_auth:
raise forms.ValidationError( raise forms.ValidationError(
"Your username and/or password were incorrect.") _("Your username and/or password were incorrect."))
return self.cleaned_data return self.cleaned_data
def clean_email(self): def clean_email(self):
@ -51,7 +52,24 @@ class LoginFormMixin(forms.Form):
CustomUser.objects.get(email=email) CustomUser.objects.get(email=email)
return email return email
except CustomUser.DoesNotExist: except CustomUser.DoesNotExist:
raise forms.ValidationError("User does not exist") raise forms.ValidationError(_("User does not exist"))
class ResendActivationEmailForm(forms.Form):
email = forms.CharField(widget=forms.EmailInput())
class Meta:
fields = ['email']
def clean_email(self):
email = self.cleaned_data.get('email')
try:
c = CustomUser.objects.get(email=email)
if c.validated == 1:
raise forms.ValidationError(_("The account is already active."))
return email
except CustomUser.DoesNotExist:
raise forms.ValidationError(_("User does not exist"))
class PasswordResetRequestForm(forms.Form): class PasswordResetRequestForm(forms.Form):
@ -66,7 +84,7 @@ class PasswordResetRequestForm(forms.Form):
CustomUser.objects.get(email=email) CustomUser.objects.get(email=email)
return email return email
except CustomUser.DoesNotExist: except CustomUser.DoesNotExist:
raise forms.ValidationError("User does not exist") raise forms.ValidationError(_("User does not exist"))
class SetPasswordForm(forms.Form): class SetPasswordForm(forms.Form):
@ -75,11 +93,11 @@ class SetPasswordForm(forms.Form):
password password
""" """
error_messages = { error_messages = {
'password_mismatch': ("The two password fields didn't match."), 'password_mismatch': _("The two password fields didn't match."),
} }
new_password1 = forms.CharField(label=("New password"), new_password1 = forms.CharField(label=_("New password"),
widget=forms.PasswordInput) widget=forms.PasswordInput)
new_password2 = forms.CharField(label=("New password confirmation"), new_password2 = forms.CharField(label=_("New password confirmation"),
widget=forms.PasswordInput) widget=forms.PasswordInput)
def clean_new_password2(self): def clean_new_password2(self):
@ -113,6 +131,12 @@ class BillingAddressForm(forms.ModelForm):
} }
class BillingAddressFormSignup(BillingAddressForm):
name = forms.CharField(label=_('Name'))
email = forms.EmailField(label=_('Email Address'))
field_order = ['name', 'email']
class UserBillingAddressForm(forms.ModelForm): class UserBillingAddressForm(forms.ModelForm):
user = forms.ModelChoiceField(queryset=CustomUser.objects.all(), user = forms.ModelChoiceField(queryset=CustomUser.objects.all(),
widget=forms.HiddenInput()) widget=forms.HiddenInput())

View file

@ -1,4 +1,10 @@
from hosting.models import UserHostingKey import logging
from oca.pool import WrongIdError
from hosting.models import UserHostingKey, VMDetail
from opennebula_api.serializers import VirtualMachineSerializer
logger = logging.getLogger(__name__)
def get_all_public_keys(customer): def get_all_public_keys(customer):
@ -9,3 +15,48 @@ def get_all_public_keys(customer):
""" """
return UserHostingKey.objects.filter(user_id=customer.id).values_list( return UserHostingKey.objects.filter(user_id=customer.id).values_list(
"public_key", flat=True) "public_key", flat=True)
def get_or_create_vm_detail(user, manager, vm_id):
"""
Returns VMDetail object related to given vm_id. Creates the object
if it does not exist
:param vm_id: The ID of the VM which should be greater than 0.
:param user: The CustomUser object that owns this VM
:param manager: The OpenNebulaManager object
:return: The VMDetail object. None if vm_id is less than or equal to 0.
Also, for the cases where the VMDetail does not exist and we can not
fetch data about the VM from OpenNebula, the function returns None
"""
if vm_id <= 0:
return None
try:
vm_detail_obj = VMDetail.objects.get(vm_id=vm_id)
except VMDetail.DoesNotExist:
try:
vm_obj = manager.get_vm(vm_id)
except (WrongIdError, ConnectionRefusedError) as e:
logger.error(str(e))
return None
vm = VirtualMachineSerializer(vm_obj).data
vm_detail_obj = VMDetail.objects.create(
user=user, vm_id=vm_id, disk_size=vm['disk_size'],
cores=vm['cores'], memory=vm['memory'],
configuration=vm['configuration'], ipv4=vm['ipv4'],
ipv6=vm['ipv6']
)
return vm_detail_obj
def get_vm_price(cpu, memory, disk_size):
"""
A helper function that computes price of a VM from given cpu, ram and
ssd parameters
:param cpu: Number of cores of the VM
:param memory: RAM of the VM
:param disk_size: Disk space of the VM
:return: The price of the VM
"""
return (cpu * 5) + (memory * 2) + (disk_size * 0.6)

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-02 11:50+0000\n" "POT-Creation-Date: 2017-09-25 20:11+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"
@ -738,6 +738,24 @@ msgstr ""
msgid "Enter your name or company name" msgid "Enter your name or company name"
msgstr "Geben Sie Ihren Namen oder der Ihrer Firma ein" msgstr "Geben Sie Ihren Namen oder der Ihrer Firma ein"
msgid "Your username and/or password were incorrect."
msgstr "Dein Benutzername und/oder Dein Passwort ist falsch."
msgid "User does not exist"
msgstr "Der Benutzer existiert nicht"
msgid "The account is already active."
msgstr "Das Benutzerkonto ist bereits aktiv."
msgid "The two password fields didn't match."
msgstr "Die beiden Passwörter stimmen nicht überein."
msgid "New password"
msgstr "Neues Passwort"
msgid "New password confirmation"
msgstr "Neues Passwort Bestätigung"
msgid "Cardholder Name" msgid "Cardholder Name"
msgstr "Name des Kartenbesitzer" msgstr "Name des Kartenbesitzer"
@ -768,8 +786,16 @@ msgstr "Telefon"
msgid "Message" msgid "Message"
msgstr "Nachricht" msgstr "Nachricht"
msgid "An email with the activation link has been sent to your email"
msgstr ""
"Der Link zum Zurücksetzen deines Passwortes wurde an deine E-Mail gesendet"
msgid "Account Activation"
msgstr "Accountaktivierung"
msgid "The link to reset your email has been sent to your email" msgid "The link to reset your email has been sent to your email"
msgstr "Der Link zum Zurücksetzen deines Passwortes wurde an deine E-Mail gesendet" msgstr ""
"Der Link zum Zurücksetzen deines Passwortes wurde an deine E-Mail gesendet"
msgid "Password Reset" msgid "Password Reset"
msgstr "Passwort zurücksetzen" msgstr "Passwort zurücksetzen"

View file

@ -238,7 +238,7 @@ class StripeUtils(object):
@staticmethod @staticmethod
def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None): def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None):
""" """
Returns the stripe plan id string of the form Returns the Stripe plan id string of the form
`dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters `dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters
:param cpu: The number of cores :param cpu: The number of cores
@ -256,7 +256,19 @@ class StripeUtils(object):
if hdd is not None: if hdd is not None:
dcl_plan_string = '{dcl_plan_string}-hdd-{hdd}gb'.format( dcl_plan_string = '{dcl_plan_string}-hdd-{hdd}gb'.format(
dcl_plan_string=dcl_plan_string, hdd=hdd) dcl_plan_string=dcl_plan_string, hdd=hdd)
stripe_plan_id_string = '{app}-v{version}-{plan}'.format(app=app, stripe_plan_id_string = '{app}-v{version}-{plan}'.format(
version=version, app=app,
plan=dcl_plan_string) version=version,
plan=dcl_plan_string)
return stripe_plan_id_string return stripe_plan_id_string
@staticmethod
def get_stripe_plan_name(cpu, memory, disk_size):
"""
Returns the Stripe plan name
:return:
"""
return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format(
cpu=cpu,
memory=memory,
disk_size=disk_size)

View file

@ -2,6 +2,7 @@ from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
@ -63,9 +64,45 @@ class LoginViewMixin(FormView):
return super(LoginViewMixin, self).get(request, *args, **kwargs) return super(LoginViewMixin, self).get(request, *args, **kwargs)
class ResendActivationLinkViewMixin(FormView):
success_message = _(
"An email with the activation link has been sent to your email")
def generate_email_context(self, user):
context = {
'base_url': "{0}://{1}".format(self.request.scheme,
self.request.get_host()),
'activation_link': reverse_lazy(
'hosting:validate',
kwargs={'validate_slug': user.validation_slug}
),
'dcl_text': settings.DCL_TEXT,
}
return context
def form_valid(self, form):
email = form.cleaned_data.get('email')
user = CustomUser.objects.get(email=email)
messages.add_message(self.request, messages.SUCCESS,
self.success_message)
context = self.generate_email_context(user)
email_data = {
'subject': '{dcl_text} {account_activation}'.format(
dcl_text=settings.DCL_TEXT,
account_activation=_('Account Activation')
),
'to': email,
'context': context,
'template_name': self.email_template_name,
'template_path': self.email_template_path,
'from_address': settings.DCL_SUPPORT_FROM_ADDRESS
}
email = BaseEmail(**email_data)
email.send()
return HttpResponseRedirect(self.get_success_url())
class PasswordResetViewMixin(FormView): class PasswordResetViewMixin(FormView):
# template_name = 'hosting/reset_password.html'
# form_class = PasswordResetRequestForm
success_message = _( success_message = _(
"The link to reset your email has been sent to your email") "The link to reset your email has been sent to your email")
site = '' site = ''
@ -78,7 +115,6 @@ class PasswordResetViewMixin(FormView):
'site_name': 'ungleich' if self.site != 'dcl' else settings.DCL_TEXT, 'site_name': 'ungleich' if self.site != 'dcl' else settings.DCL_TEXT,
'base_url': "{0}://{1}".format(self.request.scheme, 'base_url': "{0}://{1}".format(self.request.scheme,
self.request.get_host()) self.request.get_host())
} }
return context return context
@ -104,11 +140,8 @@ class PasswordResetViewMixin(FormView):
class PasswordResetConfirmViewMixin(FormView): class PasswordResetConfirmViewMixin(FormView):
# template_name = 'hosting/confirm_reset_password.html'
form_class = SetPasswordForm form_class = SetPasswordForm
# success_url = reverse_lazy('hosting:login')
def post(self, request, uidb64=None, token=None, *arg, **kwargs): def post(self, request, uidb64=None, token=None, *arg, **kwargs):
try: try:
uid = urlsafe_base64_decode(uidb64) uid = urlsafe_base64_decode(uidb64)