Compare commits
No commits in common. "master" and "2.2.1" have entirely different histories.
32 changed files with 340 additions and 1238 deletions
13
Changelog
13
Changelog
|
@ -1,16 +1,3 @@
|
||||||
Next:
|
|
||||||
* bugfix: Use correct version of django-multisite (MR #676)
|
|
||||||
2.4.1: 2018-10-18
|
|
||||||
* bugfix: Update pycryptodome module from 3.4 to 3.6.6 (PR #674)
|
|
||||||
2.4: 2018-10-18
|
|
||||||
* #5681: [hosting,dcl] Allow admin to lower minimum RAM to 512 MB (PR #672)
|
|
||||||
2.3.1: 2018-10-17
|
|
||||||
* bugfix: [hosting, dcl] Show VAT percent rounded to 2 decimal places in the order confirmation page (PR #673)
|
|
||||||
2.3: 2018-10-08
|
|
||||||
* #5690: Generic payment page - allow admin to add a onetime/monthly product and the frontend for user to pay for this product (PR #666)
|
|
||||||
2.2.2: 2018-09-28
|
|
||||||
* #5721: Set calculator OS list in alphabetical order and set `Devuan Ascii` as the default (PR #668)
|
|
||||||
* bugfix: Fix some typos and correct DE translations (PR #667)
|
|
||||||
2.2.1: 2018-09-25
|
2.2.1: 2018-09-25
|
||||||
* feature: Change DCLNavbarPlugin to show login option only if set (PR #665)
|
* feature: Change DCLNavbarPlugin to show login option only if set (PR #665)
|
||||||
* bugfix: Log opennebula errors and send proper message when vm terminate is not completed in the stipulated time (PR #648)
|
* bugfix: Log opennebula errors and send proper message when vm terminate is not completed in the stipulated time (PR #648)
|
||||||
|
|
|
@ -354,11 +354,3 @@ class DCLCalculatorPluginModel(CMSPlugin):
|
||||||
"in the backend to be automatically listed in this "
|
"in the backend to be automatically listed in this "
|
||||||
"calculator instance."
|
"calculator instance."
|
||||||
)
|
)
|
||||||
default_selected_template = models.CharField(
|
|
||||||
default="Devuan Ascii",
|
|
||||||
null=True,
|
|
||||||
max_length=128,
|
|
||||||
help_text="Write the name of the template that you need selected as"
|
|
||||||
" default when the calculator loads"
|
|
||||||
)
|
|
||||||
enable_512mb_ram = models.BooleanField(default=False)
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ from .cms_models import (
|
||||||
DCLSectionPromoPluginModel, DCLCalculatorPluginModel
|
DCLSectionPromoPluginModel, DCLCalculatorPluginModel
|
||||||
)
|
)
|
||||||
from .models import VMTemplate
|
from .models import VMTemplate
|
||||||
from datacenterlight.utils import clear_all_session_vars
|
|
||||||
|
|
||||||
|
|
||||||
@plugin_pool.register_plugin
|
@plugin_pool.register_plugin
|
||||||
|
@ -86,7 +85,6 @@ class DCLCalculatorPlugin(CMSPluginBase):
|
||||||
require_parent = True
|
require_parent = True
|
||||||
|
|
||||||
def render(self, context, instance, placeholder):
|
def render(self, context, instance, placeholder):
|
||||||
clear_all_session_vars(context['request'])
|
|
||||||
context = super(DCLCalculatorPlugin, self).render(
|
context = super(DCLCalculatorPlugin, self).render(
|
||||||
context, instance, placeholder
|
context, instance, placeholder
|
||||||
)
|
)
|
||||||
|
@ -94,13 +92,11 @@ class DCLCalculatorPlugin(CMSPluginBase):
|
||||||
if ids:
|
if ids:
|
||||||
context['templates'] = VMTemplate.objects.filter(
|
context['templates'] = VMTemplate.objects.filter(
|
||||||
vm_type=instance.vm_type
|
vm_type=instance.vm_type
|
||||||
).filter(opennebula_vm_template_id__in=ids).order_by('name')
|
).filter(opennebula_vm_template_id__in=ids)
|
||||||
else:
|
else:
|
||||||
context['templates'] = VMTemplate.objects.filter(
|
context['templates'] = VMTemplate.objects.filter(
|
||||||
vm_type=instance.vm_type
|
vm_type=instance.vm_type
|
||||||
).order_by('name')
|
)
|
||||||
context['instance'] = instance
|
|
||||||
context['min_ram'] = 0.5 if instance.enable_512mb_ram else 1
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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: 2018-09-26 20:44+0000\n"
|
"POT-Creation-Date: 2018-07-05 23:11+0000\n"
|
||||||
"PO-Revision-Date: 2018-03-30 23:22+0000\n"
|
"PO-Revision-Date: 2018-03-30 23:22+0000\n"
|
||||||
"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
|
"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -293,9 +293,6 @@ msgstr "Registrieren"
|
||||||
msgid "Billing Address"
|
msgid "Billing Address"
|
||||||
msgstr "Rechnungsadresse"
|
msgstr "Rechnungsadresse"
|
||||||
|
|
||||||
msgid "Make a payment"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Your Order"
|
msgid "Your Order"
|
||||||
msgstr "Deine Bestellung"
|
msgstr "Deine Bestellung"
|
||||||
|
|
||||||
|
@ -339,9 +336,9 @@ msgid ""
|
||||||
"database."
|
"database."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine "
|
"Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine "
|
||||||
"Kreditkartendetails unten an. Die Bezahlung wird über <a href=\"https://"
|
"Kreditkartendetails unten an. Die Bezahlung wird über "
|
||||||
"stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. Wir speichern Deine "
|
"<a href=\"https://stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. "
|
||||||
"Kreditkartendetails nicht in unserer Datenbank."
|
"Wir speichern Deine Kreditkartendetails nicht in unserer Datenbank."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please fill in your credit card information below. We are using <a href="
|
"Please fill in your credit card information below. We are using <a href="
|
||||||
|
@ -398,35 +395,12 @@ msgstr "Bestellungsübersicht"
|
||||||
msgid "Product"
|
msgid "Product"
|
||||||
msgstr "Produkt"
|
msgstr "Produkt"
|
||||||
|
|
||||||
msgid "Amount"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Description"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Recurring"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Subtotal"
|
msgid "Subtotal"
|
||||||
msgstr "Zwischensumme"
|
msgstr "Zwischensumme"
|
||||||
|
|
||||||
msgid "VAT"
|
msgid "VAT"
|
||||||
msgstr "Mehrwertsteuer"
|
msgstr "Mehrwertsteuer"
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"By clicking \"Place order\" this plan will charge your credit card account "
|
|
||||||
"with %(total_price)s CHF/month"
|
|
||||||
msgstr ""
|
|
||||||
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
|
|
||||||
"%(vm_total_price)s CHF pro Monat belastet"
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"By clicking \"Place order\" this payment will charge your credit card "
|
|
||||||
"account with a one time amount of %(total_price)s CHF"
|
|
||||||
msgstr ""
|
|
||||||
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
|
|
||||||
"%(vm_total_price)s CHF pro Monat belastet"
|
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"By clicking \"Place order\" this plan will charge your credit card account "
|
"By clicking \"Place order\" this plan will charge your credit card account "
|
||||||
|
@ -567,33 +541,8 @@ msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "An error occurred while associating the card. Details: {details}"
|
msgid "An error occurred while associating the card. Details: {details}"
|
||||||
msgstr ""
|
msgstr "Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: "
|
||||||
"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
|
"{details}"
|
||||||
|
|
||||||
msgid "Confirmation of your payment"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid " This is a monthly recurring plan."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#, python-brace-format
|
|
||||||
msgid ""
|
|
||||||
"Hi {name},\n"
|
|
||||||
"\n"
|
|
||||||
"thank you for your order!\n"
|
|
||||||
"We have just received a payment of CHF {amount:.2f} from you.{recurring}\n"
|
|
||||||
"\n"
|
|
||||||
"Cheers,\n"
|
|
||||||
"Your Data Center Light team"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Thank you for the payment."
|
|
||||||
msgstr "Danke für Deine Bestellung."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"You will soon receive a confirmation email of the payment. You can always "
|
|
||||||
"contact us at info@ungleich.ch for any question that you may have."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Thank you for the order."
|
msgid "Thank you for the order."
|
||||||
msgstr "Danke für Deine Bestellung."
|
msgstr "Danke für Deine Bestellung."
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.9.4 on 2018-09-27 20:32
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('datacenterlight', '0025_dclnavbarpluginmodel_show_login_option'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='dclcalculatorpluginmodel',
|
|
||||||
name='default_selected_template',
|
|
||||||
field=models.CharField(default='Devuan Ascii', help_text='Write the name of the template that you need selected as default when the calculator loads', max_length=128, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.9.4 on 2018-09-29 05:36
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('datacenterlight', '0026_dclcalculatorpluginmodel_default_selected_template'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='dclcalculatorpluginmodel',
|
|
||||||
name='enable_512mb_ram',
|
|
||||||
field=models.BooleanField(default=False),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -180,9 +180,3 @@ footer .dcl-link-separator::before {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-no-border {
|
|
||||||
border: none !important;
|
|
||||||
background: transparent !important;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,10 +5,6 @@
|
||||||
/* ---------------------------------------------
|
/* ---------------------------------------------
|
||||||
Scripts initialization
|
Scripts initialization
|
||||||
--------------------------------------------- */
|
--------------------------------------------- */
|
||||||
var minRam = 1;
|
|
||||||
if(window.minRam){
|
|
||||||
minRam = window.minRam;
|
|
||||||
}
|
|
||||||
var cardPricing = {
|
var cardPricing = {
|
||||||
'cpu': {
|
'cpu': {
|
||||||
'id': 'coreValue',
|
'id': 'coreValue',
|
||||||
|
@ -20,7 +16,7 @@
|
||||||
'ram': {
|
'ram': {
|
||||||
'id': 'ramValue',
|
'id': 'ramValue',
|
||||||
'value': 2,
|
'value': 2,
|
||||||
'min': minRam,
|
'min': 1,
|
||||||
'max': 200,
|
'max': 200,
|
||||||
'interval': 1
|
'interval': 1
|
||||||
},
|
},
|
||||||
|
@ -44,7 +40,6 @@
|
||||||
_initNavUrl();
|
_initNavUrl();
|
||||||
_initPricing();
|
_initPricing();
|
||||||
ajaxForms();
|
ajaxForms();
|
||||||
$('#ramValue').data('old-value', $('#ramValue').val());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$(window).resize(function() {
|
$(window).resize(function() {
|
||||||
|
@ -149,54 +144,21 @@
|
||||||
var data = $(this).data('minus');
|
var data = $(this).data('minus');
|
||||||
|
|
||||||
if (cardPricing[data].value > cardPricing[data].min) {
|
if (cardPricing[data].value > cardPricing[data].min) {
|
||||||
if(data === 'ram' && String(cardPricing[data].value) === "1" && minRam === 0.5){
|
|
||||||
cardPricing[data].value = 0.5;
|
|
||||||
$('#ramValue').val('0.5');
|
|
||||||
$("#ramValue").attr('step', 0.5);
|
|
||||||
} else {
|
|
||||||
cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
|
cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_fetchPricing();
|
_fetchPricing();
|
||||||
$('#ramValue').data('old-value', $('#ramValue').val());
|
|
||||||
});
|
});
|
||||||
$('.fa-plus-circle.right').click(function(event) {
|
$('.fa-plus-circle.right').click(function(event) {
|
||||||
var data = $(this).data('plus');
|
var data = $(this).data('plus');
|
||||||
if (cardPricing[data].value < cardPricing[data].max) {
|
if (cardPricing[data].value < cardPricing[data].max) {
|
||||||
if(data === 'ram' && String(cardPricing[data].value) === "0.5" && minRam === 0.5){
|
|
||||||
cardPricing[data].value = 1;
|
|
||||||
$('#ramValue').val('1');
|
|
||||||
$("#ramValue").attr('step', 1);
|
|
||||||
} else {
|
|
||||||
cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
|
cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_fetchPricing();
|
_fetchPricing();
|
||||||
$('#ramValue').data('old-value', $('#ramValue').val());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.input-price').change(function() {
|
$('.input-price').change(function() {
|
||||||
var data = $(this).attr("name");
|
var data = $(this).attr("name");
|
||||||
var input = $('input[name=' + data + ']');
|
cardPricing[data].value = $('input[name=' + data + ']').val();
|
||||||
var inputValue = input.val();
|
|
||||||
|
|
||||||
if(data === 'ram') {
|
|
||||||
var ramInput = $('#ramValue');
|
|
||||||
if ($('#ramValue').data('old-value') < $('#ramValue').val()) {
|
|
||||||
if($('#ramValue').val() === '1' && minRam === 0.5) {
|
|
||||||
$("#ramValue").attr('step', 1);
|
|
||||||
$('#ramValue').val('1');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if($('#ramValue').val() === '0' && minRam === 0.5) {
|
|
||||||
$("#ramValue").attr('step', 0.5);
|
|
||||||
$('#ramValue').val('0.5');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inputValue = $('#ramValue').val();
|
|
||||||
$('#ramValue').data('old-value', $('#ramValue').val());
|
|
||||||
}
|
|
||||||
cardPricing[data].value = inputValue;
|
|
||||||
_fetchPricing();
|
_fetchPricing();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,14 +9,11 @@
|
||||||
window.ssdUnitPrice = {{vm_pricing.ssd_unit_price|default:0}};
|
window.ssdUnitPrice = {{vm_pricing.ssd_unit_price|default:0}};
|
||||||
window.hddUnitPrice = {{vm_pricing.hdd_unit_price|default:0}};
|
window.hddUnitPrice = {{vm_pricing.hdd_unit_price|default:0}};
|
||||||
window.discountAmount = {{vm_pricing.discount_amount|default:0}};
|
window.discountAmount = {{vm_pricing.discount_amount|default:0}};
|
||||||
window.minRam = {{min_ram}};
|
|
||||||
window.minRamErr = '{% blocktrans with min_ram=min_ram %}Please enter a value in range {{min_ram}} - 200.{% endblocktrans %}';
|
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form id="order_form" method="POST" action="{{calculator_form_url}}" data-toggle="validator" role="form">
|
<form id="order_form" method="POST" action="{{calculator_form_url}}" data-toggle="validator" role="form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="pid" value="{{instance.id}}">
|
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h3>{% trans "VM hosting" %} </h3>
|
<h3>{% trans "VM hosting" %} </h3>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,8 +54,8 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="description input">
|
<div class="description input">
|
||||||
<i class="fa fa-minus-circle left" data-minus="ram" aria-hidden="true"></i>
|
<i class="fa fa-minus-circle left" data-minus="ram" aria-hidden="true"></i>
|
||||||
<input id="ramValue" class="input-price select-number" type="number" min="{% if min_ram == 0.5 %}0{% else %}1{% endif %}" max="200" name="ram"
|
<input id="ramValue" class="input-price select-number" type="number" min="1" max="200" name="ram"
|
||||||
data-error="{% blocktrans with min_ram=min_ram %}Please enter a value in range {{min_ram}} - 200.{% endblocktrans %}" required step="1">
|
data-error="{% trans 'Please enter a value in range 1 - 200.' %}" required>
|
||||||
<span> GB RAM</span>
|
<span> GB RAM</span>
|
||||||
<i class="fa fa-plus-circle right" data-plus="ram" aria-hidden="true"></i>
|
<i class="fa fa-plus-circle right" data-plus="ram" aria-hidden="true"></i>
|
||||||
</div>
|
</div>
|
||||||
|
@ -94,8 +91,7 @@
|
||||||
<label for="config">OS</label>
|
<label for="config">OS</label>
|
||||||
<select name="config">
|
<select name="config">
|
||||||
{% for template in templates %}
|
{% for template in templates %}
|
||||||
|
<option value="{{template.opennebula_vm_template_id}}">{{template.name}}</option>
|
||||||
<option value="{{template.opennebula_vm_template_id}}" {% if template.name|lower == instance.default_selected_template|lower %}selected="selected"{% endif %}>{{template.name}}</option>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -67,18 +67,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="dcl-payment-box">
|
<div class="dcl-payment-box">
|
||||||
<div class="dcl-payment-section">
|
<div class="dcl-payment-section">
|
||||||
{% if generic_payment_form %}
|
|
||||||
<h3>{%trans "Make a payment" %}</h3>
|
|
||||||
<hr class="top-hr">
|
|
||||||
<form role="form" id="generic-payment-form" method="post" action="" novalidate>
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="product" value="1" />
|
|
||||||
{% for field in generic_payment_form %}
|
|
||||||
{% bootstrap_field field type='fields'%}
|
|
||||||
{% endfor %}
|
|
||||||
<p class="text-danger">{{generic_payment_form.non_field_errors|striptags}}</p>
|
|
||||||
</form>
|
|
||||||
{% else %}
|
|
||||||
<h3>{%trans "Your Order" %}</h3>
|
<h3>{%trans "Your Order" %}</h3>
|
||||||
<hr class="top-hr">
|
<hr class="top-hr">
|
||||||
<div class="dcl-payment-order">
|
<div class="dcl-payment-order">
|
||||||
|
@ -109,7 +97,6 @@
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dcl-payment-box">
|
<div class="dcl-payment-box">
|
||||||
|
|
|
@ -47,32 +47,6 @@
|
||||||
<hr>
|
<hr>
|
||||||
<div>
|
<div>
|
||||||
<h4>{% trans "Order summary" %}</h4>
|
<h4>{% trans "Order summary" %}</h4>
|
||||||
{% if generic_payment_details %}
|
|
||||||
<p>
|
|
||||||
<strong>{% trans "Product" %}:</strong>
|
|
||||||
{{ generic_payment_details.product_name }}
|
|
||||||
</p>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<p>
|
|
||||||
<span>{% trans "Amount" %}: </span>
|
|
||||||
<strong class="pull-right">CHF {{generic_payment_details.amount|floatformat:2|intcomma}}</strong>
|
|
||||||
</p>
|
|
||||||
{% if generic_payment_details.description %}
|
|
||||||
<p>
|
|
||||||
<span>{% trans "Description" %}: </span>
|
|
||||||
<strong class="pull-right">{{generic_payment_details.description}}</strong>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% if generic_payment_details.recurring %}
|
|
||||||
<p>
|
|
||||||
<span>{% trans "Recurring" %}: </span>
|
|
||||||
<strong class="pull-right">Yes</strong>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p>
|
<p>
|
||||||
<strong>{% trans "Product" %}:</strong>
|
<strong>{% trans "Product" %}:</strong>
|
||||||
{{ request.session.template.name }}
|
{{ request.session.template.name }}
|
||||||
|
@ -128,7 +102,6 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<hr class="thin-hr">
|
<hr class="thin-hr">
|
||||||
</div>
|
</div>
|
||||||
|
@ -136,15 +109,7 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
{% if generic_payment_details %}
|
|
||||||
{% if generic_payment_details.recurring %}
|
|
||||||
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/month{% endblocktrans %}.</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this payment will charge your credit card account with a one time amount of {{total_price}} CHF{% endblocktrans %}.</div>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<div class="dcl-place-order-text">{% blocktrans with vm_total_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{vm_total_price}} CHF/month{% endblocktrans %}.</div>
|
<div class="dcl-place-order-text">{% blocktrans with vm_total_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{vm_total_price}} CHF/month{% endblocktrans %}.</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4 order-confirm-btn text-right">
|
<div class="col-sm-4 order-confirm-btn text-right">
|
||||||
<button class="btn choice-btn" id="btn-create-vm" data-toggle="modal" data-target="#createvm-modal">
|
<button class="btn choice-btn" id="btn-create-vm" data-toggle="modal" data-target="#createvm-modal">
|
||||||
|
@ -186,5 +151,16 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
{% trans "Some problem encountered. Please try again later." as err_msg %}
|
{% trans "Some problem encountered. Please try again later." as err_msg %}
|
||||||
var create_vm_error_message = '{{err_msg|safe}}';
|
var create_vm_error_message = '{{err_msg|safe}}';
|
||||||
|
window.onload = function () {
|
||||||
|
var locale_dates = document.getElementsByClassName("locale_date");
|
||||||
|
var formats = ['YYYY-MM-DD hh:mm a']
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < locale_dates.length; i++) {
|
||||||
|
var oldDate = moment.utc(locale_dates[i].textContent, formats);
|
||||||
|
var outputFormat = locale_dates[i].getAttribute('data-format') || oldDate._f;
|
||||||
|
locale_dates[i].innerHTML = oldDate.local().format(outputFormat);
|
||||||
|
locale_dates[i].className += ' done';
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
{%endblock%}
|
{%endblock%}
|
|
@ -89,14 +89,8 @@ def create_vm(billing_address_data, stripe_customer_id, specs,
|
||||||
|
|
||||||
create_vm_task.delay(vm_template_id, user, specs, template, order.id)
|
create_vm_task.delay(vm_template_id, user, specs, template, order.id)
|
||||||
|
|
||||||
clear_all_session_vars(request)
|
|
||||||
|
|
||||||
|
|
||||||
def clear_all_session_vars(request):
|
|
||||||
if request.session is not None:
|
|
||||||
for session_var in ['specs', 'template', 'billing_address',
|
for session_var in ['specs', 'template', 'billing_address',
|
||||||
'billing_address_data', 'card_id',
|
'billing_address_data', 'card_id',
|
||||||
'token', 'customer', 'generic_payment_type',
|
'token', 'customer']:
|
||||||
'generic_payment_details', 'product_id']:
|
|
||||||
if session_var in request.session:
|
if session_var in request.session:
|
||||||
del request.session[session_var]
|
del request.session[session_var]
|
||||||
|
|
|
@ -6,31 +6,23 @@ from django.contrib import messages
|
||||||
from django.contrib.auth import login, authenticate
|
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, JsonResponse, Http404
|
from django.http import HttpResponseRedirect, JsonResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.utils.translation import get_language, 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, DetailView
|
from django.views.generic import FormView, CreateView, DetailView
|
||||||
|
|
||||||
from hosting.forms import (
|
from hosting.forms import HostingUserLoginForm
|
||||||
HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm
|
from hosting.models import HostingOrder, UserCardDetail
|
||||||
)
|
|
||||||
from hosting.models import (
|
|
||||||
HostingBill, HostingOrder, UserCardDetail, GenericProduct
|
|
||||||
)
|
|
||||||
from membership.models import CustomUser, StripeCustomer
|
from membership.models import CustomUser, StripeCustomer
|
||||||
from opennebula_api.serializers import VMTemplateSerializer
|
from opennebula_api.serializers import VMTemplateSerializer
|
||||||
from utils.forms import (
|
from utils.forms import BillingAddressForm, BillingAddressFormSignup
|
||||||
BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm,
|
|
||||||
BillingAddress
|
|
||||||
)
|
|
||||||
from utils.hosting_utils import get_vm_price_with_vat
|
from utils.hosting_utils import get_vm_price_with_vat
|
||||||
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 .cms_models import DCLCalculatorPluginModel
|
|
||||||
from .forms import ContactForm
|
from .forms import ContactForm
|
||||||
from .models import VMTemplate, VMPricing
|
from .models import VMTemplate, VMPricing
|
||||||
from .utils import get_cms_integration, create_vm, clear_all_session_vars
|
from .utils import get_cms_integration, create_vm
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -64,7 +56,7 @@ class ContactUsView(FormView):
|
||||||
sender=form.cleaned_data.get('email')
|
sender=form.cleaned_data.get('email')
|
||||||
),
|
),
|
||||||
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
|
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
|
||||||
'to': [from_emails.get(from_page, 'support@ungleich.ch')],
|
'to': [from_emails.get(from_page, 'info@ungleich.ch')],
|
||||||
'body': "\n".join(
|
'body': "\n".join(
|
||||||
["%s=%s" % (k, v) for (k, v) in form.cleaned_data.items()]),
|
["%s=%s" % (k, v) for (k, v) in form.cleaned_data.items()]),
|
||||||
'reply_to': [form.cleaned_data.get('email')],
|
'reply_to': [form.cleaned_data.get('email')],
|
||||||
|
@ -90,29 +82,7 @@ class IndexView(CreateView):
|
||||||
raise ValidationError(_('Invalid number of cores'))
|
raise ValidationError(_('Invalid number of cores'))
|
||||||
|
|
||||||
def validate_memory(self, value):
|
def validate_memory(self, value):
|
||||||
if 'pid' in self.request.POST:
|
if (value > 200) or (value < 1):
|
||||||
try:
|
|
||||||
plugin = DCLCalculatorPluginModel.objects.get(
|
|
||||||
id=self.request.POST['pid']
|
|
||||||
)
|
|
||||||
except DCLCalculatorPluginModel.DoesNotExist as dne:
|
|
||||||
logger.error(
|
|
||||||
str(dne) + " plugin_id: " + self.request.POST['pid']
|
|
||||||
)
|
|
||||||
raise ValidationError(_('Invalid calculator properties'))
|
|
||||||
if plugin.enable_512mb_ram:
|
|
||||||
if value % 1 == 0 or value == 0.5:
|
|
||||||
logger.debug(
|
|
||||||
"Given ram {value} is either 0.5 or a"
|
|
||||||
" whole number".format(value=value)
|
|
||||||
)
|
|
||||||
if (value > 200) or (value < 0.5):
|
|
||||||
raise ValidationError(_('Invalid RAM size'))
|
|
||||||
else:
|
|
||||||
raise ValidationError(_('Invalid RAM size'))
|
|
||||||
elif (value > 200) or (value < 1) or (value % 1 != 0):
|
|
||||||
raise ValidationError(_('Invalid RAM size'))
|
|
||||||
else:
|
|
||||||
raise ValidationError(_('Invalid RAM size'))
|
raise ValidationError(_('Invalid RAM size'))
|
||||||
|
|
||||||
def validate_storage(self, value):
|
def validate_storage(self, value):
|
||||||
|
@ -121,14 +91,17 @@ class IndexView(CreateView):
|
||||||
|
|
||||||
@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):
|
||||||
clear_all_session_vars(request)
|
for session_var in ['specs', 'user', 'billing_address_data',
|
||||||
|
'pricing_name']:
|
||||||
|
if session_var in request.session:
|
||||||
|
del request.session[session_var]
|
||||||
return HttpResponseRedirect(reverse('datacenterlight:cms_index'))
|
return HttpResponseRedirect(reverse('datacenterlight:cms_index'))
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
cores = request.POST.get('cpu')
|
cores = request.POST.get('cpu')
|
||||||
cores_field = forms.IntegerField(validators=[self.validate_cores])
|
cores_field = forms.IntegerField(validators=[self.validate_cores])
|
||||||
memory = request.POST.get('ram')
|
memory = request.POST.get('ram')
|
||||||
memory_field = forms.FloatField(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])
|
||||||
template_id = int(request.POST.get('config'))
|
template_id = int(request.POST.get('config'))
|
||||||
|
@ -197,7 +170,7 @@ class IndexView(CreateView):
|
||||||
'vat': vat,
|
'vat': vat,
|
||||||
'vat_percent': vat_percent,
|
'vat_percent': vat_percent,
|
||||||
'discount': discount,
|
'discount': discount,
|
||||||
'total_price': round(price + vat - discount['amount'], 2),
|
'total_price': price + vat - discount['amount'],
|
||||||
'pricing_name': vm_pricing_name
|
'pricing_name': vm_pricing_name
|
||||||
}
|
}
|
||||||
request.session['specs'] = specs
|
request.session['specs'] = specs
|
||||||
|
@ -269,93 +242,19 @@ class PaymentOrderView(FormView):
|
||||||
'login_form': HostingUserLoginForm(prefix='login_form'),
|
'login_form': HostingUserLoginForm(prefix='login_form'),
|
||||||
'billing_address_form': billing_address_form,
|
'billing_address_form': billing_address_form,
|
||||||
'cms_integration': get_cms_integration('default'),
|
'cms_integration': get_cms_integration('default'),
|
||||||
})
|
|
||||||
|
|
||||||
if ('generic_payment_type' in self.request.session and
|
|
||||||
self.request.session['generic_payment_type'] == 'generic'):
|
|
||||||
if 'product_id' in self.request.session:
|
|
||||||
product = GenericProduct.objects.get(
|
|
||||||
id=self.request.session['product_id']
|
|
||||||
)
|
|
||||||
context.update({'generic_payment_form': ProductPaymentForm(
|
|
||||||
prefix='generic_payment_form',
|
|
||||||
initial={'product_name': product.product_name,
|
|
||||||
'amount': float(product.get_actual_price()),
|
|
||||||
'recurring': product.product_is_subscription,
|
|
||||||
'description': product.product_description,
|
|
||||||
},
|
|
||||||
product_id=product.id
|
|
||||||
), })
|
|
||||||
else:
|
|
||||||
context.update({'generic_payment_form': GenericPaymentForm(
|
|
||||||
prefix='generic_payment_form',
|
|
||||||
), })
|
|
||||||
else:
|
|
||||||
context.update({
|
|
||||||
'vm_pricing': VMPricing.get_vm_pricing_by_name(
|
'vm_pricing': VMPricing.get_vm_pricing_by_name(
|
||||||
self.request.session['specs']['pricing_name']
|
self.request.session['specs']['pricing_name']
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
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 (('type' in request.GET and request.GET['type'] == 'generic')
|
if 'specs' not in request.session:
|
||||||
or 'product_slug' in kwargs):
|
|
||||||
request.session['generic_payment_type'] = 'generic'
|
|
||||||
if 'generic_payment_details' in request.session:
|
|
||||||
request.session.pop('generic_payment_details')
|
|
||||||
request.session.pop('product_id')
|
|
||||||
if 'product_slug' in kwargs:
|
|
||||||
logger.debug("Product slug is " + kwargs['product_slug'])
|
|
||||||
try:
|
|
||||||
product = GenericProduct.objects.get(
|
|
||||||
product_slug=kwargs['product_slug']
|
|
||||||
)
|
|
||||||
except GenericProduct.DoesNotExist as dne:
|
|
||||||
logger.error(
|
|
||||||
"Product '{}' does "
|
|
||||||
"not exist".format(kwargs['product_slug'])
|
|
||||||
)
|
|
||||||
raise Http404()
|
|
||||||
request.session['product_id'] = product.id
|
|
||||||
elif '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):
|
||||||
if 'product' in request.POST:
|
|
||||||
# query for the supplied product
|
|
||||||
product = None
|
|
||||||
try:
|
|
||||||
product = GenericProduct.objects.get(
|
|
||||||
id=request.POST['generic_payment_form-product_name']
|
|
||||||
)
|
|
||||||
except GenericProduct.DoesNotExist as dne:
|
|
||||||
logger.error(
|
|
||||||
"The requested product '{}' does not exist".format(
|
|
||||||
request.POST['generic_payment_form-product_name']
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except GenericProduct.MultipleObjectsReturned as mpe:
|
|
||||||
logger.error(
|
|
||||||
"There seem to be more than one product with "
|
|
||||||
"the name {}".format(
|
|
||||||
request.POST['generic_payment_form-product_name']
|
|
||||||
)
|
|
||||||
)
|
|
||||||
product = GenericProduct.objects.all(
|
|
||||||
product_name=request.
|
|
||||||
POST['generic_payment_form-product_name']
|
|
||||||
).first()
|
|
||||||
if product is None:
|
|
||||||
return JsonResponse({})
|
|
||||||
else:
|
|
||||||
return JsonResponse({
|
|
||||||
'amount': product.get_actual_price(),
|
|
||||||
'isSubscription': product.product_is_subscription
|
|
||||||
})
|
|
||||||
if 'login_form' in request.POST:
|
if 'login_form' in request.POST:
|
||||||
login_form = HostingUserLoginForm(
|
login_form = HostingUserLoginForm(
|
||||||
data=request.POST, prefix='login_form'
|
data=request.POST, prefix='login_form'
|
||||||
|
@ -366,13 +265,6 @@ class PaymentOrderView(FormView):
|
||||||
auth_user = authenticate(email=email, password=password)
|
auth_user = authenticate(email=email, password=password)
|
||||||
if auth_user:
|
if auth_user:
|
||||||
login(self.request, auth_user)
|
login(self.request, auth_user)
|
||||||
if 'product_slug' in kwargs:
|
|
||||||
return HttpResponseRedirect(
|
|
||||||
reverse('show_product',
|
|
||||||
kwargs={
|
|
||||||
'product_slug': kwargs['product_slug']}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
reverse('datacenterlight:payment')
|
reverse('datacenterlight:payment')
|
||||||
)
|
)
|
||||||
|
@ -389,50 +281,6 @@ class PaymentOrderView(FormView):
|
||||||
data=request.POST,
|
data=request.POST,
|
||||||
)
|
)
|
||||||
if address_form.is_valid():
|
if address_form.is_valid():
|
||||||
# Check if we are in a generic payment case and handle the generic
|
|
||||||
# payment details form before we go on to verify payment
|
|
||||||
if ('generic_payment_type' in request.session and
|
|
||||||
self.request.session['generic_payment_type'] == 'generic'):
|
|
||||||
if 'product_id' in request.session:
|
|
||||||
generic_payment_form = ProductPaymentForm(
|
|
||||||
data=request.POST, prefix='generic_payment_form',
|
|
||||||
product_id=request.session['product_id']
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
generic_payment_form = GenericPaymentForm(
|
|
||||||
data=request.POST, prefix='generic_payment_form'
|
|
||||||
)
|
|
||||||
if generic_payment_form.is_valid():
|
|
||||||
logger.debug("Generic payment form is valid.")
|
|
||||||
if 'product_id' in request.session:
|
|
||||||
product = generic_payment_form.product
|
|
||||||
else:
|
|
||||||
product = generic_payment_form.cleaned_data.get(
|
|
||||||
'product_name'
|
|
||||||
)
|
|
||||||
gp_details = {
|
|
||||||
"product_name": product.product_name,
|
|
||||||
"amount": generic_payment_form.cleaned_data.get(
|
|
||||||
'amount'
|
|
||||||
),
|
|
||||||
"recurring": generic_payment_form.cleaned_data.get(
|
|
||||||
'recurring'
|
|
||||||
),
|
|
||||||
"description": generic_payment_form.cleaned_data.get(
|
|
||||||
'description'
|
|
||||||
),
|
|
||||||
"product_id": product.id,
|
|
||||||
"product_slug": product.product_slug
|
|
||||||
}
|
|
||||||
request.session["generic_payment_details"] = (
|
|
||||||
gp_details
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.debug("Generic payment form invalid")
|
|
||||||
context = self.get_context_data()
|
|
||||||
context['generic_payment_form'] = generic_payment_form
|
|
||||||
context['billing_address_form'] = address_form
|
|
||||||
return self.render_to_response(context)
|
|
||||||
token = address_form.cleaned_data.get('token')
|
token = address_form.cleaned_data.get('token')
|
||||||
if token is '':
|
if token is '':
|
||||||
card_id = address_form.cleaned_data.get('card')
|
card_id = address_form.cleaned_data.get('card')
|
||||||
|
@ -538,8 +386,7 @@ class OrderConfirmationView(DetailView):
|
||||||
@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):
|
||||||
context = {}
|
context = {}
|
||||||
if (('specs' not in request.session or 'user' not in request.session)
|
if 'specs' not in request.session or 'user' not in request.session:
|
||||||
and 'generic_payment_type' not in request.session):
|
|
||||||
return HttpResponseRedirect(reverse('datacenterlight:index'))
|
return HttpResponseRedirect(reverse('datacenterlight:index'))
|
||||||
if 'token' in self.request.session:
|
if 'token' in self.request.session:
|
||||||
token = self.request.session['token']
|
token = self.request.session['token']
|
||||||
|
@ -557,19 +404,9 @@ class OrderConfirmationView(DetailView):
|
||||||
card_detail = UserCardDetail.objects.get(id=card_id)
|
card_detail = UserCardDetail.objects.get(id=card_id)
|
||||||
context['cc_last4'] = card_detail.last4
|
context['cc_last4'] = card_detail.last4
|
||||||
context['cc_brand'] = card_detail.brand
|
context['cc_brand'] = card_detail.brand
|
||||||
|
|
||||||
if ('generic_payment_type' in request.session and
|
|
||||||
self.request.session['generic_payment_type'] == 'generic'):
|
|
||||||
context.update({
|
|
||||||
'generic_payment_details':
|
|
||||||
request.session['generic_payment_details'],
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
context.update({
|
|
||||||
'vm': request.session.get('specs'),
|
|
||||||
})
|
|
||||||
context.update({
|
context.update({
|
||||||
'site_url': reverse('datacenterlight:index'),
|
'site_url': reverse('datacenterlight:index'),
|
||||||
|
'vm': request.session.get('specs'),
|
||||||
'page_header_text': _('Confirm Order'),
|
'page_header_text': _('Confirm Order'),
|
||||||
'billing_address_data': (
|
'billing_address_data': (
|
||||||
request.session.get('billing_address_data')
|
request.session.get('billing_address_data')
|
||||||
|
@ -579,8 +416,11 @@ class OrderConfirmationView(DetailView):
|
||||||
return render(request, self.template_name, context)
|
return render(request, self.template_name, context)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
template = request.session.get('template')
|
||||||
|
specs = request.session.get('specs')
|
||||||
user = request.session.get('user')
|
user = request.session.get('user')
|
||||||
stripe_api_cus_id = request.session.get('customer')
|
stripe_api_cus_id = request.session.get('customer')
|
||||||
|
vm_template_id = template.get('id', 1)
|
||||||
stripe_utils = StripeUtils()
|
stripe_utils = StripeUtils()
|
||||||
|
|
||||||
if 'token' in request.session:
|
if 'token' in request.session:
|
||||||
|
@ -594,14 +434,7 @@ class OrderConfirmationView(DetailView):
|
||||||
response = {
|
response = {
|
||||||
'status': False,
|
'status': False,
|
||||||
'redirect': "{url}#{section}".format(
|
'redirect': "{url}#{section}".format(
|
||||||
url=(reverse(
|
url=reverse('datacenterlight:payment'),
|
||||||
'show_product',
|
|
||||||
kwargs={'product_slug':
|
|
||||||
request.session['generic_payment_details']
|
|
||||||
['product_slug']}
|
|
||||||
) if 'generic_payment_details' in request.session else
|
|
||||||
reverse('datacenterlight:payment')
|
|
||||||
),
|
|
||||||
section='payment_error'),
|
section='payment_error'),
|
||||||
'msg_title': str(_('Error.')),
|
'msg_title': str(_('Error.')),
|
||||||
'msg_body': str(
|
'msg_body': str(
|
||||||
|
@ -617,8 +450,7 @@ class OrderConfirmationView(DetailView):
|
||||||
'brand': card_details_response['brand'],
|
'brand': card_details_response['brand'],
|
||||||
'card_id': card_details_response['card_id']
|
'card_id': card_details_response['card_id']
|
||||||
}
|
}
|
||||||
stripe_customer_obj = StripeCustomer.objects.filter(
|
stripe_customer_obj = StripeCustomer.objects.filter(stripe_id=stripe_api_cus_id).first()
|
||||||
stripe_id=stripe_api_cus_id).first()
|
|
||||||
if stripe_customer_obj:
|
if stripe_customer_obj:
|
||||||
ucd = UserCardDetail.get_user_card_details(
|
ucd = UserCardDetail.get_user_card_details(
|
||||||
stripe_customer_obj, card_details_response
|
stripe_customer_obj, card_details_response
|
||||||
|
@ -640,16 +472,7 @@ class OrderConfirmationView(DetailView):
|
||||||
response = {
|
response = {
|
||||||
'status': False,
|
'status': False,
|
||||||
'redirect': "{url}#{section}".format(
|
'redirect': "{url}#{section}".format(
|
||||||
url=(reverse(
|
url=reverse('hosting:payment'),
|
||||||
'show_product',
|
|
||||||
kwargs={'product_slug':
|
|
||||||
request.session
|
|
||||||
['generic_payment_details']
|
|
||||||
['product_slug']}
|
|
||||||
) if 'generic_payment_details' in
|
|
||||||
request.session else
|
|
||||||
reverse('datacenterlight:payment')
|
|
||||||
),
|
|
||||||
section='payment_error'),
|
section='payment_error'),
|
||||||
'msg_title': str(_('Error.')),
|
'msg_title': str(_('Error.')),
|
||||||
'msg_body': str(
|
'msg_body': str(
|
||||||
|
@ -681,63 +504,6 @@ class OrderConfirmationView(DetailView):
|
||||||
}
|
}
|
||||||
return JsonResponse(response)
|
return JsonResponse(response)
|
||||||
|
|
||||||
if ('generic_payment_type' in request.session and
|
|
||||||
self.request.session['generic_payment_type'] == 'generic'):
|
|
||||||
gp_details = self.request.session['generic_payment_details']
|
|
||||||
if gp_details['recurring']:
|
|
||||||
# generic recurring payment
|
|
||||||
logger.debug("Commencing a generic recurring payment")
|
|
||||||
else:
|
|
||||||
# generic one time payment
|
|
||||||
logger.debug("Commencing a one time payment")
|
|
||||||
charge_response = stripe_utils.make_charge(
|
|
||||||
amount=gp_details['amount'],
|
|
||||||
customer=stripe_api_cus_id
|
|
||||||
)
|
|
||||||
stripe_onetime_charge = charge_response.get('response_object')
|
|
||||||
|
|
||||||
# Check if the payment was approved
|
|
||||||
if not stripe_onetime_charge:
|
|
||||||
msg = charge_response.get('error')
|
|
||||||
messages.add_message(self.request, messages.ERROR, msg,
|
|
||||||
extra_tags='failed_payment')
|
|
||||||
response = {
|
|
||||||
'status': False,
|
|
||||||
'redirect': "{url}#{section}".format(
|
|
||||||
url=(reverse('show_product', kwargs={
|
|
||||||
'product_slug': gp_details['product_slug']}
|
|
||||||
) if 'generic_payment_details' in
|
|
||||||
request.session else
|
|
||||||
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 JsonResponse(response)
|
|
||||||
|
|
||||||
if ('generic_payment_type' not in request.session or
|
|
||||||
(request.session['generic_payment_details']['recurring'])):
|
|
||||||
if 'generic_payment_details' in request.session:
|
|
||||||
amount_to_be_charged = (
|
|
||||||
round(
|
|
||||||
request.session['generic_payment_details']['amount'],
|
|
||||||
2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
plan_name = "generic-{0}-{1:.2f}".format(
|
|
||||||
request.session['generic_payment_details']['product_id'],
|
|
||||||
amount_to_be_charged
|
|
||||||
)
|
|
||||||
stripe_plan_id = plan_name
|
|
||||||
else:
|
|
||||||
template = request.session.get('template')
|
|
||||||
specs = request.session.get('specs')
|
|
||||||
vm_template_id = template.get('id', 1)
|
|
||||||
|
|
||||||
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')
|
||||||
|
@ -780,16 +546,8 @@ class OrderConfirmationView(DetailView):
|
||||||
response = {
|
response = {
|
||||||
'status': False,
|
'status': False,
|
||||||
'redirect': "{url}#{section}".format(
|
'redirect': "{url}#{section}".format(
|
||||||
url=(reverse(
|
url=reverse('datacenterlight:payment'),
|
||||||
'show_product',
|
section='payment_error'),
|
||||||
kwargs={'product_slug':
|
|
||||||
request.session['generic_payment_details']
|
|
||||||
['product_slug']}
|
|
||||||
) if 'generic_payment_details' in request.session else
|
|
||||||
reverse('datacenterlight:payment')
|
|
||||||
),
|
|
||||||
section='payment_error'
|
|
||||||
),
|
|
||||||
'msg_title': str(_('Error.')),
|
'msg_title': str(_('Error.')),
|
||||||
'msg_body': str(
|
'msg_body': str(
|
||||||
_('There was a payment related error.'
|
_('There was a payment related error.'
|
||||||
|
@ -866,118 +624,6 @@ class OrderConfirmationView(DetailView):
|
||||||
'user': custom_user.id
|
'user': custom_user.id
|
||||||
})
|
})
|
||||||
|
|
||||||
if 'generic_payment_type' in request.session:
|
|
||||||
stripe_cus = StripeCustomer.objects.filter(
|
|
||||||
stripe_id=stripe_api_cus_id
|
|
||||||
).first()
|
|
||||||
billing_address = BillingAddress(
|
|
||||||
cardholder_name=billing_address_data['cardholder_name'],
|
|
||||||
street_address=billing_address_data['street_address'],
|
|
||||||
city=billing_address_data['city'],
|
|
||||||
postal_code=billing_address_data['postal_code'],
|
|
||||||
country=billing_address_data['country']
|
|
||||||
)
|
|
||||||
billing_address.save()
|
|
||||||
|
|
||||||
order = HostingOrder.create(
|
|
||||||
price=self.request
|
|
||||||
.session['generic_payment_details']['amount'],
|
|
||||||
customer=stripe_cus,
|
|
||||||
billing_address=billing_address,
|
|
||||||
vm_pricing=VMPricing.get_default_pricing()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create a Hosting Bill
|
|
||||||
HostingBill.create(customer=stripe_cus,
|
|
||||||
billing_address=billing_address)
|
|
||||||
|
|
||||||
# Create Billing Address for User if he does not have one
|
|
||||||
if not stripe_cus.user.billing_addresses.count():
|
|
||||||
billing_address_data.update({
|
|
||||||
'user': stripe_cus.user.id
|
|
||||||
})
|
|
||||||
billing_address_user_form = UserBillingAddressForm(
|
|
||||||
billing_address_data
|
|
||||||
)
|
|
||||||
billing_address_user_form.is_valid()
|
|
||||||
billing_address_user_form.save()
|
|
||||||
|
|
||||||
if self.request.session['generic_payment_details']['recurring']:
|
|
||||||
# Associate the given stripe subscription with the order
|
|
||||||
order.set_subscription_id(
|
|
||||||
stripe_subscription_obj.id, card_details_dict
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Associate the given stripe charge id with the order
|
|
||||||
order.set_stripe_charge(stripe_onetime_charge)
|
|
||||||
|
|
||||||
# Set order status approved
|
|
||||||
order.set_approved()
|
|
||||||
order.generic_payment_description = gp_details["description"]
|
|
||||||
order.generic_product_id = gp_details["product_id"]
|
|
||||||
order.save()
|
|
||||||
# send emails
|
|
||||||
context = {
|
|
||||||
'name': user.get('name'),
|
|
||||||
'email': user.get('email'),
|
|
||||||
'amount': gp_details['amount'],
|
|
||||||
'description': gp_details['description'],
|
|
||||||
'recurring': gp_details['recurring'],
|
|
||||||
'product_name': gp_details['product_name'],
|
|
||||||
'product_id': gp_details['product_id'],
|
|
||||||
'order_id': order.id
|
|
||||||
}
|
|
||||||
|
|
||||||
email_data = {
|
|
||||||
'subject': (settings.DCL_TEXT +
|
|
||||||
" Payment received from %s" % context['email']),
|
|
||||||
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
|
|
||||||
'to': ['info@ungleich.ch'],
|
|
||||||
'body': "\n".join(
|
|
||||||
["%s=%s" % (k, v) for (k, v) in context.items()]),
|
|
||||||
'reply_to': [context['email']],
|
|
||||||
}
|
|
||||||
send_plain_email_task.delay(email_data)
|
|
||||||
|
|
||||||
email_data = {
|
|
||||||
'subject': _("Confirmation of your payment"),
|
|
||||||
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
|
|
||||||
'to': [user.get('email')],
|
|
||||||
'body': _("Hi {name},\n\n"
|
|
||||||
"thank you for your order!\n"
|
|
||||||
"We have just received a payment of CHF {amount:.2f}"
|
|
||||||
" from you.{recurring}\n\n"
|
|
||||||
"Cheers,\nYour Data Center Light team".format(
|
|
||||||
name=user.get('name'),
|
|
||||||
amount=gp_details['amount'],
|
|
||||||
recurring=(
|
|
||||||
_(' This is a monthly recurring plan.')
|
|
||||||
if gp_details['recurring'] else ''
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
'reply_to': ['info@ungleich.ch'],
|
|
||||||
}
|
|
||||||
send_plain_email_task.delay(email_data)
|
|
||||||
|
|
||||||
response = {
|
|
||||||
'status': True,
|
|
||||||
'redirect': (
|
|
||||||
reverse('hosting:orders')
|
|
||||||
if request.user.is_authenticated()
|
|
||||||
else reverse('datacenterlight:index')
|
|
||||||
),
|
|
||||||
'msg_title': str(_('Thank you for the payment.')),
|
|
||||||
'msg_body': str(
|
|
||||||
_('You will soon receive a confirmation email of the '
|
|
||||||
'payment. You can always contact us at '
|
|
||||||
'info@ungleich.ch for any question that you may have.')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
clear_all_session_vars(request)
|
|
||||||
|
|
||||||
return JsonResponse(response)
|
|
||||||
|
|
||||||
user = {
|
user = {
|
||||||
'name': custom_user.name,
|
'name': custom_user.name,
|
||||||
'email': custom_user.email,
|
'email': custom_user.email,
|
||||||
|
|
|
@ -10,7 +10,6 @@ from django.conf import settings
|
||||||
from hosting.views import (
|
from hosting.views import (
|
||||||
RailsHostingView, DjangoHostingView, NodeJSHostingView
|
RailsHostingView, DjangoHostingView, NodeJSHostingView
|
||||||
)
|
)
|
||||||
from datacenterlight.views import PaymentOrderView
|
|
||||||
from membership import urls as membership_urls
|
from membership import urls as membership_urls
|
||||||
from ungleich_page.views import LandingView
|
from ungleich_page.views import LandingView
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
|
@ -30,9 +29,6 @@ urlpatterns = [
|
||||||
url(r'^nosystemd/', include('nosystemd.urls', namespace="nosystemd")),
|
url(r'^nosystemd/', include('nosystemd.urls', namespace="nosystemd")),
|
||||||
url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')),
|
url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')),
|
||||||
url(r'^jsi18n/(?P<packages>\S+?)/$', i18n.javascript_catalog),
|
url(r'^jsi18n/(?P<packages>\S+?)/$', i18n.javascript_catalog),
|
||||||
url(r'^product/(?P<product_slug>[\w-]+)/$',
|
|
||||||
PaymentOrderView.as_view(),
|
|
||||||
name='show_product'),
|
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
urlpatterns += i18n_patterns(
|
urlpatterns += i18n_patterns(
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import HostingOrder, HostingBill, HostingPlan, GenericProduct
|
from .models import HostingOrder, HostingBill, HostingPlan
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(HostingOrder)
|
admin.site.register(HostingOrder)
|
||||||
admin.site.register(HostingBill)
|
admin.site.register(HostingBill)
|
||||||
admin.site.register(HostingPlan)
|
admin.site.register(HostingPlan)
|
||||||
admin.site.register(GenericProduct)
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from membership.models import CustomUser
|
from membership.models import CustomUser
|
||||||
from utils.hosting_utils import get_all_public_keys
|
from utils.hosting_utils import get_all_public_keys
|
||||||
from .models import UserHostingKey, GenericProduct
|
from .models import UserHostingKey
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -52,93 +52,6 @@ class HostingUserLoginForm(forms.Form):
|
||||||
raise forms.ValidationError(_("User does not exist"))
|
raise forms.ValidationError(_("User does not exist"))
|
||||||
|
|
||||||
|
|
||||||
class ProductModelChoiceField(forms.ModelChoiceField):
|
|
||||||
def label_from_instance(self, obj):
|
|
||||||
return obj.product_name
|
|
||||||
|
|
||||||
|
|
||||||
class GenericPaymentForm(forms.Form):
|
|
||||||
product_name = ProductModelChoiceField(
|
|
||||||
queryset=GenericProduct.objects.all().order_by('product_name'),
|
|
||||||
empty_label=_("Choose a product"),
|
|
||||||
)
|
|
||||||
amount = forms.FloatField(
|
|
||||||
widget=forms.TextInput(
|
|
||||||
attrs={'placeholder': _('Amount in CHF'),
|
|
||||||
'readonly': 'readonly', }
|
|
||||||
),
|
|
||||||
max_value=999999,
|
|
||||||
min_value=1,
|
|
||||||
label=_('Amount in CHF')
|
|
||||||
)
|
|
||||||
recurring = forms.BooleanField(required=False,
|
|
||||||
label=_("Recurring monthly"), )
|
|
||||||
description = forms.CharField(
|
|
||||||
widget=forms.Textarea(attrs={'style': "height: 60px;"}),
|
|
||||||
required=False
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = GenericProduct
|
|
||||||
fields = ['product_name', 'amount', 'recurring', 'description']
|
|
||||||
|
|
||||||
def clean_amount(self):
|
|
||||||
amount = self.cleaned_data.get('amount')
|
|
||||||
if (float(self.cleaned_data.get('product_name').get_actual_price()) !=
|
|
||||||
amount):
|
|
||||||
raise forms.ValidationError(_("Amount field does not match"))
|
|
||||||
return amount
|
|
||||||
|
|
||||||
def clean_recurring(self):
|
|
||||||
recurring = self.cleaned_data.get('recurring')
|
|
||||||
if (self.cleaned_data.get('product_name').product_is_subscription !=
|
|
||||||
(True if recurring else False)):
|
|
||||||
raise forms.ValidationError(_("Recurring field does not match"))
|
|
||||||
return recurring
|
|
||||||
|
|
||||||
|
|
||||||
class ProductPaymentForm(GenericPaymentForm):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
product_id = kwargs.pop('product_id', None)
|
|
||||||
if product_id is not None:
|
|
||||||
self.product = GenericProduct.objects.get(id=product_id)
|
|
||||||
super(ProductPaymentForm, self).__init__(*args, **kwargs)
|
|
||||||
self.fields['product_name'] = forms.CharField(
|
|
||||||
widget=forms.TextInput(
|
|
||||||
attrs={'placeholder': _('Product name'),
|
|
||||||
'readonly': 'readonly'}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if self.product.product_is_subscription:
|
|
||||||
self.fields['amount'].label = "{amt} ({payment_type})".format(
|
|
||||||
amt=_('Amount in CHF'),
|
|
||||||
payment_type=_('Monthly subscription')
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.fields['amount'].label = "{amt} ({payment_type})".format(
|
|
||||||
amt=_('Amount in CHF'),
|
|
||||||
payment_type=_('One time payment')
|
|
||||||
)
|
|
||||||
self.fields['recurring'].widget = forms.HiddenInput()
|
|
||||||
self.fields['product_name'].widget.attrs['class'] = 'input-no-border'
|
|
||||||
self.fields['amount'].widget.attrs['class'] = 'input-no-border'
|
|
||||||
self.fields['description'].widget.attrs['class'] = 'input-no-border'
|
|
||||||
|
|
||||||
def clean_amount(self):
|
|
||||||
amount = self.cleaned_data.get('amount')
|
|
||||||
if (self.product is None or
|
|
||||||
float(self.product.get_actual_price()) != amount):
|
|
||||||
raise forms.ValidationError(_("Amount field does not match"))
|
|
||||||
return amount
|
|
||||||
|
|
||||||
def clean_recurring(self):
|
|
||||||
recurring = self.cleaned_data.get('recurring')
|
|
||||||
if (self.product.product_is_subscription !=
|
|
||||||
(True if recurring else False)):
|
|
||||||
raise forms.ValidationError(_("Recurring field does not match"))
|
|
||||||
return recurring
|
|
||||||
|
|
||||||
|
|
||||||
class HostingUserSignupForm(forms.ModelForm):
|
class HostingUserSignupForm(forms.ModelForm):
|
||||||
confirm_password = forms.CharField(label=_("Confirm Password"),
|
confirm_password = forms.CharField(label=_("Confirm Password"),
|
||||||
widget=forms.PasswordInput())
|
widget=forms.PasswordInput())
|
||||||
|
|
|
@ -209,7 +209,7 @@ msgstr "Du hast eine neue virtuelle Maschine bestellt!"
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Your order of <strong>%(vm_name)s</strong> has been charged."
|
msgid "Your order of <strong>%(vm_name)s</strong> has been charged."
|
||||||
msgstr "Deine Bestellung von <strong>%(vm_name)s</strong> wurde entgegengenommen."
|
msgstr "Deine Bestellung von <strong>%(vm_name)s</strong> wurde erhoben."
|
||||||
|
|
||||||
msgid "You can view your VM detail by clicking the button below."
|
msgid "You can view your VM detail by clicking the button below."
|
||||||
msgstr "Um die Rechnung zu sehen, klicke auf den Button unten."
|
msgstr "Um die Rechnung zu sehen, klicke auf den Button unten."
|
||||||
|
@ -222,7 +222,7 @@ msgstr "Dein Data Center Light Team"
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Your order of %(vm_name)s has been charged."
|
msgid "Your order of %(vm_name)s has been charged."
|
||||||
msgstr "Deine Bestellung von %(vm_name)s wurde entgegengenommen."
|
msgstr "Deine Bestellung von %(vm_name)s wurde erhoben."
|
||||||
|
|
||||||
msgid "You can view your VM detail by following the link below."
|
msgid "You can view your VM detail by following the link below."
|
||||||
msgstr "Um die Rechnung zu sehen, klicke auf den Link unten."
|
msgstr "Um die Rechnung zu sehen, klicke auf den Link unten."
|
||||||
|
@ -249,7 +249,7 @@ msgstr "VM Kündigung"
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You are receiving this email because your virtual machine <strong>"
|
"You are receiving this email because your virutal machine <strong>"
|
||||||
"%(vm_name)s</strong> has been cancelled."
|
"%(vm_name)s</strong> has been cancelled."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Du erhälst diese E-Mail, da deine virtuelle Maschine <strong>%(vm_name)s</"
|
"Du erhälst diese E-Mail, da deine virtuelle Maschine <strong>%(vm_name)s</"
|
||||||
|
@ -265,7 +265,7 @@ msgstr "NEUE VM"
|
||||||
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You are receiving this email because your virtual machine %(vm_name)s has "
|
"You are receiving this email because your virutal machine %(vm_name)s has "
|
||||||
"been cancelled."
|
"been cancelled."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Du erhälst diese E-Mail, da deine virtuelle Maschine %(vm_name)s gekündigt "
|
"Du erhälst diese E-Mail, da deine virtuelle Maschine %(vm_name)s gekündigt "
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.9.4 on 2018-10-03 07:57
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import utils.mixins
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hosting', '0047_auto_20180821_1240'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='GenericProduct',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('product_name', models.CharField(default='', max_length=128)),
|
|
||||||
('product_slug', models.SlugField(help_text='An optional html id for the Section. Required to set as target of a link on page', unique=True)),
|
|
||||||
('product_description', models.CharField(default='', max_length=500)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('product_price', models.DecimalField(decimal_places=2, max_digits=6)),
|
|
||||||
('product_vat', models.DecimalField(decimal_places=4, default=0, max_digits=6)),
|
|
||||||
('product_is_subscription', models.BooleanField(default=True)),
|
|
||||||
],
|
|
||||||
bases=(utils.mixins.AssignPermissionsMixin, models.Model),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hostingorder',
|
|
||||||
name='generic_payment_description',
|
|
||||||
field=models.CharField(max_length=500, null=True),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='hostingorder',
|
|
||||||
name='generic_product',
|
|
||||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.GenericProduct'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.9.4 on 2018-10-05 07:36
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('hosting', '0048_auto_20181003_0757'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='genericproduct',
|
|
||||||
name='product_slug',
|
|
||||||
field=models.SlugField(help_text='An mandatory unique slug for the product', unique=True),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -9,8 +9,8 @@ from django.utils.functional import cached_property
|
||||||
|
|
||||||
from datacenterlight.models import VMPricing, VMTemplate
|
from datacenterlight.models import VMPricing, VMTemplate
|
||||||
from membership.models import StripeCustomer, CustomUser
|
from membership.models import StripeCustomer, CustomUser
|
||||||
from utils.mixins import AssignPermissionsMixin
|
|
||||||
from utils.models import BillingAddress
|
from utils.models import BillingAddress
|
||||||
|
from utils.mixins import AssignPermissionsMixin
|
||||||
from utils.stripe_utils import StripeUtils
|
from utils.stripe_utils import StripeUtils
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -61,30 +61,6 @@ class OrderDetail(AssignPermissionsMixin, models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GenericProduct(AssignPermissionsMixin, models.Model):
|
|
||||||
permissions = ('view_genericproduct',)
|
|
||||||
product_name = models.CharField(max_length=128, default="")
|
|
||||||
product_slug = models.SlugField(
|
|
||||||
unique=True,
|
|
||||||
help_text=(
|
|
||||||
'An mandatory unique slug for the product'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
product_description = models.CharField(max_length=500, default="")
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
product_price = models.DecimalField(max_digits=6, decimal_places=2)
|
|
||||||
product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0)
|
|
||||||
product_is_subscription = models.BooleanField(default=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.product_name
|
|
||||||
|
|
||||||
def get_actual_price(self):
|
|
||||||
return round(
|
|
||||||
self.product_price + (self.product_price * self.product_vat), 2
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class HostingOrder(AssignPermissionsMixin, models.Model):
|
class HostingOrder(AssignPermissionsMixin, models.Model):
|
||||||
ORDER_APPROVED_STATUS = 'Approved'
|
ORDER_APPROVED_STATUS = 'Approved'
|
||||||
ORDER_DECLINED_STATUS = 'Declined'
|
ORDER_DECLINED_STATUS = 'Declined'
|
||||||
|
@ -104,13 +80,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
|
||||||
OrderDetail, null=True, blank=True, default=None,
|
OrderDetail, null=True, blank=True, default=None,
|
||||||
on_delete=models.SET_NULL
|
on_delete=models.SET_NULL
|
||||||
)
|
)
|
||||||
generic_product = models.ForeignKey(
|
|
||||||
GenericProduct, null=True, blank=True, default=None,
|
|
||||||
on_delete=models.SET_NULL
|
|
||||||
)
|
|
||||||
generic_payment_description = models.CharField(
|
|
||||||
max_length=500, null=True
|
|
||||||
)
|
|
||||||
permissions = ('view_hostingorder',)
|
permissions = ('view_hostingorder',)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -119,18 +89,11 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
hosting_order_str = ("Order Nr: #{} - VM_ID: {} - {} - {} - "
|
return ("Order Nr: #{} - VM_ID: {} - {} - {} - "
|
||||||
"Specs: {} - Price: {}").format(
|
"Specs: {} - Price: {}").format(
|
||||||
self.id, self.vm_id, self.customer.user.email, self.created_at,
|
self.id, self.vm_id, self.customer.user.email, self.created_at,
|
||||||
self.order_detail, self.price
|
self.order_detail, self.price
|
||||||
)
|
)
|
||||||
if self.generic_product_id is not None:
|
|
||||||
hosting_order_str += " - Generic Payment"
|
|
||||||
if self.stripe_charge_id is not None:
|
|
||||||
hosting_order_str += " - One time charge"
|
|
||||||
else:
|
|
||||||
hosting_order_str += " - Recurring"
|
|
||||||
return hosting_order_str
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def status(self):
|
def status(self):
|
||||||
|
|
|
@ -157,10 +157,6 @@ $( document ).ready(function() {
|
||||||
/* ---------------------------------------------
|
/* ---------------------------------------------
|
||||||
Scripts initialization
|
Scripts initialization
|
||||||
--------------------------------------------- */
|
--------------------------------------------- */
|
||||||
var minRam = 1;
|
|
||||||
if(window.minRam){
|
|
||||||
minRam = window.minRam;
|
|
||||||
}
|
|
||||||
var cardPricing = {
|
var cardPricing = {
|
||||||
'cpu': {
|
'cpu': {
|
||||||
'id': 'coreValue',
|
'id': 'coreValue',
|
||||||
|
@ -172,7 +168,7 @@ $( document ).ready(function() {
|
||||||
'ram': {
|
'ram': {
|
||||||
'id': 'ramValue',
|
'id': 'ramValue',
|
||||||
'value': 2,
|
'value': 2,
|
||||||
'min': minRam,
|
'min': 1,
|
||||||
'max': 200,
|
'max': 200,
|
||||||
'interval': 1
|
'interval': 1
|
||||||
},
|
},
|
||||||
|
@ -192,54 +188,21 @@ $( document ).ready(function() {
|
||||||
var data = $(this).data('minus');
|
var data = $(this).data('minus');
|
||||||
|
|
||||||
if (cardPricing[data].value > cardPricing[data].min) {
|
if (cardPricing[data].value > cardPricing[data].min) {
|
||||||
if(data === 'ram' && String(cardPricing[data].value) === "1" && minRam === 0.5){
|
|
||||||
cardPricing[data].value = 0.5;
|
|
||||||
$('#ramValue').val('0.5');
|
|
||||||
$("#ramValue").attr('step', 0.5);
|
|
||||||
} else {
|
|
||||||
cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
|
cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_fetchPricing();
|
_fetchPricing();
|
||||||
$('#ramValue').data('old-value', $('#ramValue').val());
|
|
||||||
});
|
});
|
||||||
$('.fa-plus-circle.right').click(function(event) {
|
$('.fa-plus-circle.right').click(function(event) {
|
||||||
var data = $(this).data('plus');
|
var data = $(this).data('plus');
|
||||||
if (cardPricing[data].value < cardPricing[data].max) {
|
if (cardPricing[data].value < cardPricing[data].max) {
|
||||||
if(data === 'ram' && String(cardPricing[data].value) === "0.5" && minRam === 0.5){
|
|
||||||
cardPricing[data].value = 1;
|
|
||||||
$('#ramValue').val('1');
|
|
||||||
$("#ramValue").attr('step', 1);
|
|
||||||
} else {
|
|
||||||
cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
|
cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_fetchPricing();
|
_fetchPricing();
|
||||||
$('#ramValue').data('old-value', $('#ramValue').val());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.input-price').change(function() {
|
$('.input-price').change(function() {
|
||||||
var data = $(this).attr("name");
|
var data = $(this).attr("name");
|
||||||
var input = $('input[name=' + data + ']');
|
cardPricing[data].value = $('input[name=' + data + ']').val();
|
||||||
var inputValue = input.val();
|
|
||||||
|
|
||||||
if(data === 'ram') {
|
|
||||||
var ramInput = $('#ramValue');
|
|
||||||
if ($('#ramValue').data('old-value') < $('#ramValue').val()) {
|
|
||||||
if($('#ramValue').val() === '1' && minRam === 0.5) {
|
|
||||||
$("#ramValue").attr('step', 1);
|
|
||||||
$('#ramValue').val('1');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if($('#ramValue').val() === '0' && minRam === 0.5) {
|
|
||||||
$("#ramValue").attr('step', 0.5);
|
|
||||||
$('#ramValue').val('0.5');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inputValue = $('#ramValue').val();
|
|
||||||
$('#ramValue').data('old-value', $('#ramValue').val());
|
|
||||||
}
|
|
||||||
cardPricing[data].value = inputValue;
|
|
||||||
_fetchPricing();
|
_fetchPricing();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -273,5 +236,4 @@ $( document ).ready(function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_initPricing();
|
_initPricing();
|
||||||
$('#ramValue').data('old-value', $('#ramValue').val());
|
|
||||||
});
|
});
|
|
@ -22,39 +22,6 @@ function setBrandIcon(brand) {
|
||||||
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$(function () {
|
|
||||||
$("select#id_generic_payment_form-product_name").change(function () {
|
|
||||||
var gp_form = $('#generic-payment-form');
|
|
||||||
$.ajax({
|
|
||||||
url: gp_form.attr('action'),
|
|
||||||
type: 'POST',
|
|
||||||
data: gp_form.serialize(),
|
|
||||||
init: function () {
|
|
||||||
console.log("init")
|
|
||||||
},
|
|
||||||
success: function (data) {
|
|
||||||
if (data.amount !== undefined) {
|
|
||||||
$("#id_generic_payment_form-amount").val(data.amount);
|
|
||||||
if (data.isSubscription) {
|
|
||||||
$('#id_generic_payment_form-recurring').prop('checked', true);
|
|
||||||
} else {
|
|
||||||
$('#id_generic_payment_form-recurring').prop('checked', false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$("#id_generic_payment_form-amount").val('');
|
|
||||||
$('#id_generic_payment_form-recurring').prop('checked', false);
|
|
||||||
console.log("No product found")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function (xmlhttprequest, textstatus, message) {
|
|
||||||
$("#id_generic_payment_form-amount").val('');
|
|
||||||
$('#id_generic_payment_form-recurring').prop('checked', false);
|
|
||||||
console.log("Error fetching product")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
$.ajaxSetup({
|
$.ajaxSetup({
|
||||||
beforeSend: function (xhr, settings) {
|
beforeSend: function (xhr, settings) {
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
|
@ -157,35 +124,17 @@ $(document).ready(function () {
|
||||||
$('#billing-form').submit();
|
$('#billing-form').submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCookie(name) {
|
|
||||||
var value = "; " + document.cookie;
|
|
||||||
var parts = value.split("; " + name + "=");
|
|
||||||
if (parts.length === 2) return parts.pop().split(";").shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitBillingForm() {
|
|
||||||
var billing_form = $('#billing-form');
|
|
||||||
var recurring_input = $('#id_generic_payment_form-recurring');
|
|
||||||
billing_form.append('<input type="hidden" name="generic_payment_form-product_name" value="' + $('#id_generic_payment_form-product_name').val() + '" />');
|
|
||||||
billing_form.append('<input type="hidden" name="generic_payment_form-amount" value="' + $('#id_generic_payment_form-amount').val() + '" />');
|
|
||||||
if (recurring_input.attr('type') === 'hidden') {
|
|
||||||
billing_form.append('<input type="hidden" name="generic_payment_form-recurring" value="' + (recurring_input.val() === 'True' ? 'on' : '') + '" />');
|
|
||||||
} else {
|
|
||||||
billing_form.append('<input type="hidden" name="generic_payment_form-recurring" value="' + (recurring_input.prop('checked') ? 'on' : '') + '" />');
|
|
||||||
}
|
|
||||||
billing_form.append('<input type="hidden" name="generic_payment_form-description" value="' + $('#id_generic_payment_form-description').val() + '" />');
|
|
||||||
billing_form.submit();
|
|
||||||
}
|
|
||||||
|
|
||||||
var $form_new = $('#payment-form-new');
|
var $form_new = $('#payment-form-new');
|
||||||
$form_new.submit(payWithStripe_new);
|
$form_new.submit(payWithStripe_new);
|
||||||
|
|
||||||
function payWithStripe_new(e) {
|
function payWithStripe_new(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
function stripeTokenHandler(token) {
|
function stripeTokenHandler(token) {
|
||||||
// Insert the token ID into the form so it gets submitted to the server
|
// Insert the token ID into the form so it gets submitted to the server
|
||||||
$('#id_token').val(token.id);
|
$('#id_token').val(token.id);
|
||||||
submitBillingForm();
|
$('#billing-form').submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -250,7 +199,7 @@ $(document).ready(function () {
|
||||||
$('.credit-card-info .btn.choice-btn').click(function(){
|
$('.credit-card-info .btn.choice-btn').click(function(){
|
||||||
var id = this.dataset['id_card'];
|
var id = this.dataset['id_card'];
|
||||||
$('#id_card').val(id);
|
$('#id_card').val(id);
|
||||||
submitBillingForm();
|
$('#billing-form').submit();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -134,15 +134,3 @@ $(document).ready(function() {
|
||||||
$(this).find('.modal-footer .btn').addClass('hide');
|
$(this).find('.modal-footer .btn').addClass('hide');
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
window.onload = function () {
|
|
||||||
var locale_dates = document.getElementsByClassName("locale_date");
|
|
||||||
var formats = ['YYYY-MM-DD hh:mm a'];
|
|
||||||
var i;
|
|
||||||
for (i = 0; i < locale_dates.length; i++) {
|
|
||||||
var oldDate = moment.utc(locale_dates[i].textContent, formats);
|
|
||||||
var outputFormat = locale_dates[i].getAttribute('data-format') || oldDate._f;
|
|
||||||
locale_dates[i].innerHTML = oldDate.local().format(outputFormat);
|
|
||||||
locale_dates[i].className += ' done';
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -25,7 +25,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding-top: 25px; font-size: 16px;">
|
<td style="padding-top: 25px; font-size: 16px;">
|
||||||
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
|
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
|
||||||
{% blocktrans %}You are receiving this email because your virtual machine <strong>{{ vm_name }}</strong> has been cancelled.{% endblocktrans %}
|
{% blocktrans %}You are receiving this email because your virutal machine <strong>{{ vm_name }}</strong> has been cancelled.{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
|
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
|
||||||
{% blocktrans %}You can always order a new VM by clicking the button below.{% endblocktrans %}
|
{% blocktrans %}You can always order a new VM by clicking the button below.{% endblocktrans %}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
{% trans "Virtual Machine Cancellation" %}
|
{% trans "Virtual Machine Cancellation" %}
|
||||||
|
|
||||||
{% blocktrans %}You are receiving this email because your virtual machine {{vm_name}} has been cancelled.{% endblocktrans %}
|
{% blocktrans %}You are receiving this email because your virutal machine {{vm_name}} has been cancelled.{% endblocktrans %}
|
||||||
{% blocktrans %}You can always order a new VM by following the link below.{% endblocktrans %}
|
{% blocktrans %}You can always order a new VM by following the link below.{% endblocktrans %}
|
||||||
|
|
||||||
{{ base_url }}{% url 'hosting:create_virtual_machine' %}
|
{{ base_url }}{% url 'hosting:create_virtual_machine' %}
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
{% if order and vm %}
|
{% if order %}
|
||||||
<p>
|
<p>
|
||||||
<strong>{% trans "Status" %}: </strong>
|
<strong>{% trans "Status" %}: </strong>
|
||||||
<strong>
|
<strong>
|
||||||
|
@ -93,7 +93,6 @@
|
||||||
<hr>
|
<hr>
|
||||||
<div>
|
<div>
|
||||||
<h4>{% trans "Order summary" %}</h4>
|
<h4>{% trans "Order summary" %}</h4>
|
||||||
{% if vm %}
|
|
||||||
<p>
|
<p>
|
||||||
<strong>{% trans "Product" %}:</strong>
|
<strong>{% trans "Product" %}:</strong>
|
||||||
{% if vm.name %}
|
{% if vm.name %}
|
||||||
|
@ -165,32 +164,6 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
|
||||||
<p>
|
|
||||||
<strong>{% trans "Product" %}:</strong>
|
|
||||||
{{ product_name }}
|
|
||||||
</p>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<p>
|
|
||||||
<span>{% trans "Amount" %}: </span>
|
|
||||||
<strong class="pull-right">{{order.price|floatformat:2|intcomma}} CHF</strong>
|
|
||||||
</p>
|
|
||||||
{% if order.generic_payment_description %}
|
|
||||||
<p>
|
|
||||||
<span>{% trans "Description" %}: </span>
|
|
||||||
<strong class="pull-right">{{order.generic_payment_description}}</strong>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
{% if order.subscription_id %}
|
|
||||||
<p>
|
|
||||||
<span>{% trans "Recurring" %}: </span>
|
|
||||||
<strong class="pull-right">{{order.created_at|date:'d'|ordinal}} {% trans "of every month" %}</strong>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<hr class="thin-hr">
|
<hr class="thin-hr">
|
||||||
</div>
|
</div>
|
||||||
|
@ -256,6 +229,17 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
{% trans "Some problem encountered. Please try again later." as err_msg %}
|
{% trans "Some problem encountered. Please try again later." as err_msg %}
|
||||||
var create_vm_error_message = '{{err_msg|safe}}';
|
var create_vm_error_message = '{{err_msg|safe}}';
|
||||||
|
window.onload = function () {
|
||||||
|
var locale_dates = document.getElementsByClassName("locale_date");
|
||||||
|
var formats = ['YYYY-MM-DD hh:mm a']
|
||||||
|
var i;
|
||||||
|
for (i = 0; i < locale_dates.length; i++) {
|
||||||
|
var oldDate = moment.utc(locale_dates[i].textContent, formats);
|
||||||
|
var outputFormat = locale_dates[i].getAttribute('data-format') || oldDate._f;
|
||||||
|
locale_dates[i].innerHTML = oldDate.local().format(outputFormat);
|
||||||
|
locale_dates[i].className += ' done';
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
{%endblock%}
|
{%endblock%}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
{% for order in orders %}
|
{% for order in orders %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td>
|
<td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td>
|
||||||
<td class="xs-td-bighalf locale_date" data-header="{% trans 'Date' %}">{{ order.created_at | date:'Y-m-d h:i a' }}</td>
|
<td class="xs-td-bighalf" data-header="{% trans 'Date' %}">{{ order.created_at | date:"M d, Y H:i" }}</td>
|
||||||
<td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|floatformat:2|intcomma }}</td>
|
<td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|floatformat:2|intcomma }}</td>
|
||||||
<td class="text-right last-td">
|
<td class="text-right last-td">
|
||||||
<a class="btn btn-order-detail" href="{% url 'hosting:orders' order.pk %}">{% trans 'See Invoice' %}</a>
|
<a class="btn btn-order-detail" href="{% url 'hosting:orders' order.pk %}">{% trans 'See Invoice' %}</a>
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
<h3>{%trans "Billing Address" %}</h3>
|
<h3>{%trans "Billing Address" %}</h3>
|
||||||
<hr>
|
<hr>
|
||||||
<form role="form" id="billing-form" method="post" action="" novalidate>
|
<form role="form" id="billing-form" method="post" action="" novalidate>
|
||||||
{% csrf_token %}
|
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
|
{% csrf_token %}
|
||||||
{% bootstrap_field field show_label=False type='fields' bound_css_class='' %}
|
{% bootstrap_field field show_label=False type='fields' bound_css_class='' %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="form-group text-right">
|
<div class="form-group text-right">
|
||||||
|
|
|
@ -32,7 +32,6 @@ from stored_messages.api import mark_read
|
||||||
from stored_messages.models import Message
|
from stored_messages.models import Message
|
||||||
from stored_messages.settings import stored_messages_settings
|
from stored_messages.settings import stored_messages_settings
|
||||||
|
|
||||||
from datacenterlight.cms_models import DCLCalculatorPluginModel
|
|
||||||
from datacenterlight.models import VMTemplate, VMPricing
|
from datacenterlight.models import VMTemplate, VMPricing
|
||||||
from datacenterlight.utils import create_vm, get_cms_integration
|
from datacenterlight.utils import create_vm, get_cms_integration
|
||||||
from hosting.models import UserCardDetail
|
from hosting.models import UserCardDetail
|
||||||
|
@ -60,8 +59,7 @@ from .forms import (
|
||||||
)
|
)
|
||||||
from .mixins import ProcessVMSelectionMixin, HostingContextMixin
|
from .mixins import ProcessVMSelectionMixin, HostingContextMixin
|
||||||
from .models import (
|
from .models import (
|
||||||
HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail,
|
HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail
|
||||||
GenericProduct
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -864,15 +862,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
if obj.generic_product_id is not None:
|
|
||||||
# generic payment case
|
|
||||||
logger.debug("Generic payment case")
|
|
||||||
context['product_name'] = GenericProduct.objects.get(
|
|
||||||
id=obj.generic_product_id
|
|
||||||
).product_name
|
|
||||||
else:
|
|
||||||
# invoice for previous order
|
# invoice for previous order
|
||||||
logger.debug("Invoice of VM order")
|
|
||||||
try:
|
try:
|
||||||
vm_detail = VMDetail.objects.get(vm_id=obj.vm_id)
|
vm_detail = VMDetail.objects.get(vm_id=obj.vm_id)
|
||||||
context['vm'] = vm_detail.__dict__
|
context['vm'] = vm_detail.__dict__
|
||||||
|
@ -1199,29 +1189,7 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
|
||||||
raise ValidationError(_('Invalid number of cores'))
|
raise ValidationError(_('Invalid number of cores'))
|
||||||
|
|
||||||
def validate_memory(self, value):
|
def validate_memory(self, value):
|
||||||
if 'pid' in self.request.POST:
|
if (value > 200) or (value < 1):
|
||||||
try:
|
|
||||||
plugin = DCLCalculatorPluginModel.objects.get(
|
|
||||||
id=self.request.POST['pid']
|
|
||||||
)
|
|
||||||
except DCLCalculatorPluginModel.DoesNotExist as dne:
|
|
||||||
logger.error(
|
|
||||||
str(dne) + " plugin_id: " + self.request.POST['pid']
|
|
||||||
)
|
|
||||||
raise ValidationError(_('Invalid calculator properties'))
|
|
||||||
if plugin.enable_512mb_ram:
|
|
||||||
if value % 1 == 0 or value == 0.5:
|
|
||||||
logger.debug(
|
|
||||||
"Given ram {value} is either 0.5 or a"
|
|
||||||
" whole number".format(value=value)
|
|
||||||
)
|
|
||||||
if (value > 200) or (value < 0.5):
|
|
||||||
raise ValidationError(_('Invalid RAM size'))
|
|
||||||
else:
|
|
||||||
raise ValidationError(_('Invalid RAM size'))
|
|
||||||
elif (value > 200) or (value < 1) or (value % 1 != 0):
|
|
||||||
raise ValidationError(_('Invalid RAM size'))
|
|
||||||
else:
|
|
||||||
raise ValidationError(_('Invalid RAM size'))
|
raise ValidationError(_('Invalid RAM size'))
|
||||||
|
|
||||||
def validate_storage(self, value):
|
def validate_storage(self, value):
|
||||||
|
@ -1241,7 +1209,7 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
|
||||||
cores = request.POST.get('cpu')
|
cores = request.POST.get('cpu')
|
||||||
cores_field = forms.IntegerField(validators=[self.validate_cores])
|
cores_field = forms.IntegerField(validators=[self.validate_cores])
|
||||||
memory = request.POST.get('ram')
|
memory = request.POST.get('ram')
|
||||||
memory_field = forms.FloatField(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])
|
||||||
template_id = int(request.POST.get('config'))
|
template_id = int(request.POST.get('config'))
|
||||||
|
@ -1305,7 +1273,7 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
|
||||||
'price': price,
|
'price': price,
|
||||||
'vat': vat,
|
'vat': vat,
|
||||||
'vat_percent': vat_percent,
|
'vat_percent': vat_percent,
|
||||||
'total_price': round(price + vat - discount['amount'], 2),
|
'total_price': price + vat - discount['amount'],
|
||||||
'pricing_name': vm_pricing_name
|
'pricing_name': vm_pricing_name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -249,8 +249,8 @@ class OpenNebulaManager():
|
||||||
vm_specs = vm_specs_formatter.format(
|
vm_specs = vm_specs_formatter.format(
|
||||||
vcpu=int(specs['cpu']),
|
vcpu=int(specs['cpu']),
|
||||||
cpu=0.1 * int(specs['cpu']),
|
cpu=0.1 * int(specs['cpu']),
|
||||||
memory=(512 if specs['memory'] == 0.5 else
|
memory=1024 * int(specs['memory']),
|
||||||
1024 * int(specs['memory'])),
|
|
||||||
)
|
)
|
||||||
vm_specs += """<DISK>
|
vm_specs += """<DISK>
|
||||||
<TYPE>fs</TYPE>
|
<TYPE>fs</TYPE>
|
||||||
|
@ -269,8 +269,8 @@ class OpenNebulaManager():
|
||||||
vm_specs = vm_specs_formatter.format(
|
vm_specs = vm_specs_formatter.format(
|
||||||
vcpu=int(specs['cpu']),
|
vcpu=int(specs['cpu']),
|
||||||
cpu=0.1 * int(specs['cpu']),
|
cpu=0.1 * int(specs['cpu']),
|
||||||
memory=(512 if specs['memory'] == 0.5 else
|
memory=1024 * int(specs['memory']),
|
||||||
1024 * int(specs['memory'])),
|
|
||||||
)
|
)
|
||||||
vm_specs += """<DISK>
|
vm_specs += """<DISK>
|
||||||
<TYPE>fs</TYPE>
|
<TYPE>fs</TYPE>
|
||||||
|
|
|
@ -34,6 +34,7 @@ django-meta==1.2
|
||||||
django-meta-mixin==0.3.0
|
django-meta-mixin==0.3.0
|
||||||
django-model-utils==2.5
|
django-model-utils==2.5
|
||||||
django-mptt==0.8.4
|
django-mptt==0.8.4
|
||||||
|
django-multisite==1.4.1
|
||||||
django-parler==1.6.3
|
django-parler==1.6.3
|
||||||
django-phonenumber-field==1.1.0
|
django-phonenumber-field==1.1.0
|
||||||
django-polymorphic==0.9.2
|
django-polymorphic==0.9.2
|
||||||
|
@ -68,7 +69,7 @@ model-mommy==1.2.6
|
||||||
phonenumbers==7.4.0
|
phonenumbers==7.4.0
|
||||||
phonenumberslite==7.4.0
|
phonenumberslite==7.4.0
|
||||||
psycopg2==2.7.3.2
|
psycopg2==2.7.3.2
|
||||||
pycryptodome==3.6.6
|
pycryptodome==3.4
|
||||||
pylibmc==1.5.1
|
pylibmc==1.5.1
|
||||||
python-dateutil==2.5.3
|
python-dateutil==2.5.3
|
||||||
python-slugify==1.2.0
|
python-slugify==1.2.0
|
||||||
|
|
|
@ -130,7 +130,7 @@ def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0,
|
||||||
'amount': round(float(pricing.discount_amount), 2)
|
'amount': round(float(pricing.discount_amount), 2)
|
||||||
}
|
}
|
||||||
return (round(float(price), 2), round(float(vat), 2),
|
return (round(float(price), 2), round(float(vat), 2),
|
||||||
round(float(vat_percent), 2), discount)
|
round(float(vat_percent)), discount)
|
||||||
|
|
||||||
|
|
||||||
def ping_ok(host_ipv6):
|
def ping_ok(host_ipv6):
|
||||||
|
|
Loading…
Reference in a new issue