Merge branch 'master' into task/3530/upgrade_to_django_1.11
This commit is contained in:
commit
70fc8ed6df
34 changed files with 1272 additions and 348 deletions
|
|
@ -1,8 +1,8 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import HostingOrder, HostingBill, HostingPlan
|
||||
|
||||
from .models import HostingOrder, HostingBill, HostingPlan, GenericProduct
|
||||
|
||||
admin.site.register(HostingOrder)
|
||||
admin.site.register(HostingBill)
|
||||
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 utils.hosting_utils import get_all_public_keys
|
||||
from .models import UserHostingKey
|
||||
from .models import UserHostingKey, GenericProduct
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -52,6 +52,93 @@ class HostingUserLoginForm(forms.Form):
|
|||
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):
|
||||
confirm_password = forms.CharField(label=_("Confirm Password"),
|
||||
widget=forms.PasswordInput())
|
||||
|
|
@ -111,7 +198,7 @@ class UserHostingKeyForm(forms.ModelForm):
|
|||
public_key=openssh_pubkey_str).first().name
|
||||
KEY_EXISTS_MESSAGE = _(
|
||||
"This key exists already with the name \"%(name)s\"") % {
|
||||
'name': key_name}
|
||||
'name': key_name}
|
||||
raise forms.ValidationError(KEY_EXISTS_MESSAGE)
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=True) as tmp_public_key_file:
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ msgstr "Du hast eine neue virtuelle Maschine bestellt!"
|
|||
|
||||
#, python-format
|
||||
msgid "Your order of <strong>%(vm_name)s</strong> has been charged."
|
||||
msgstr "Deine Bestellung von <strong>%(vm_name)s</strong> wurde erhoben."
|
||||
msgstr "Deine Bestellung von <strong>%(vm_name)s</strong> wurde entgegengenommen."
|
||||
|
||||
msgid "You can view your VM detail by clicking the button below."
|
||||
msgstr "Um die Rechnung zu sehen, klicke auf den Button unten."
|
||||
|
|
@ -222,7 +222,7 @@ msgstr "Dein Data Center Light Team"
|
|||
|
||||
#, python-format
|
||||
msgid "Your order of %(vm_name)s has been charged."
|
||||
msgstr "Deine Bestellung von %(vm_name)s wurde erhoben."
|
||||
msgstr "Deine Bestellung von %(vm_name)s wurde entgegengenommen."
|
||||
|
||||
msgid "You can view your VM detail by following the link below."
|
||||
msgstr "Um die Rechnung zu sehen, klicke auf den Link unten."
|
||||
|
|
@ -249,7 +249,7 @@ msgstr "VM Kündigung"
|
|||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You are receiving this email because your virutal machine <strong>"
|
||||
"You are receiving this email because your virtual machine <strong>"
|
||||
"%(vm_name)s</strong> has been cancelled."
|
||||
msgstr ""
|
||||
"Du erhälst diese E-Mail, da deine virtuelle Maschine <strong>%(vm_name)s</"
|
||||
|
|
@ -265,7 +265,7 @@ msgstr "NEUE VM"
|
|||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You are receiving this email because your virutal machine %(vm_name)s has "
|
||||
"You are receiving this email because your virtual machine %(vm_name)s has "
|
||||
"been cancelled."
|
||||
msgstr ""
|
||||
"Du erhälst diese E-Mail, da deine virtuelle Maschine %(vm_name)s gekündigt "
|
||||
|
|
|
|||
41
hosting/migrations/0048_auto_20181003_0757.py
Normal file
41
hosting/migrations/0048_auto_20181003_0757.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# -*- 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'),
|
||||
),
|
||||
]
|
||||
20
hosting/migrations/0049_auto_20181005_0736.py
Normal file
20
hosting/migrations/0049_auto_20181005_0736.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# -*- 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 membership.models import StripeCustomer, CustomUser
|
||||
from utils.models import BillingAddress
|
||||
from utils.mixins import AssignPermissionsMixin
|
||||
from utils.models import BillingAddress
|
||||
from utils.stripe_utils import StripeUtils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -61,6 +61,30 @@ 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):
|
||||
ORDER_APPROVED_STATUS = 'Approved'
|
||||
ORDER_DECLINED_STATUS = 'Declined'
|
||||
|
|
@ -80,7 +104,13 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
|
|||
OrderDetail, null=True, blank=True, default=None,
|
||||
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',)
|
||||
|
||||
class Meta:
|
||||
|
|
@ -89,11 +119,18 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
|
|||
)
|
||||
|
||||
def __str__(self):
|
||||
return ("Order Nr: #{} - VM_ID: {} - {} - {} - "
|
||||
"Specs: {} - Price: {}").format(
|
||||
hosting_order_str = ("Order Nr: #{} - VM_ID: {} - {} - {} - "
|
||||
"Specs: {} - Price: {}").format(
|
||||
self.id, self.vm_id, self.customer.user.email, self.created_at,
|
||||
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
|
||||
def status(self):
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@
|
|||
|
||||
.vm-vmid .alert {
|
||||
margin-top: 15px;
|
||||
margin-bottom: -60px;
|
||||
margin-bottom: -25px;
|
||||
}
|
||||
|
||||
.vm-item-lg {
|
||||
|
|
|
|||
|
|
@ -157,6 +157,10 @@ $( document ).ready(function() {
|
|||
/* ---------------------------------------------
|
||||
Scripts initialization
|
||||
--------------------------------------------- */
|
||||
var minRam = 1;
|
||||
if(window.minRam){
|
||||
minRam = window.minRam;
|
||||
}
|
||||
var cardPricing = {
|
||||
'cpu': {
|
||||
'id': 'coreValue',
|
||||
|
|
@ -168,7 +172,7 @@ $( document ).ready(function() {
|
|||
'ram': {
|
||||
'id': 'ramValue',
|
||||
'value': 2,
|
||||
'min': 1,
|
||||
'min': minRam,
|
||||
'max': 200,
|
||||
'interval': 1
|
||||
},
|
||||
|
|
@ -188,21 +192,54 @@ $( document ).ready(function() {
|
|||
var data = $(this).data('minus');
|
||||
|
||||
if (cardPricing[data].value > cardPricing[data].min) {
|
||||
cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
|
||||
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;
|
||||
}
|
||||
}
|
||||
_fetchPricing();
|
||||
$('#ramValue').data('old-value', $('#ramValue').val());
|
||||
});
|
||||
$('.fa-plus-circle.right').click(function(event) {
|
||||
var data = $(this).data('plus');
|
||||
if (cardPricing[data].value < cardPricing[data].max) {
|
||||
cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
|
||||
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;
|
||||
}
|
||||
}
|
||||
_fetchPricing();
|
||||
$('#ramValue').data('old-value', $('#ramValue').val());
|
||||
});
|
||||
|
||||
$('.input-price').change(function() {
|
||||
var data = $(this).attr("name");
|
||||
cardPricing[data].value = $('input[name=' + data + ']').val();
|
||||
var input = $('input[name=' + data + ']');
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
|
@ -236,4 +273,5 @@ $( document ).ready(function() {
|
|||
}
|
||||
|
||||
_initPricing();
|
||||
});
|
||||
$('#ramValue').data('old-value', $('#ramValue').val());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,6 +22,39 @@ function setBrandIcon(brand) {
|
|||
|
||||
|
||||
$(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({
|
||||
beforeSend: function (xhr, settings) {
|
||||
function getCookie(name) {
|
||||
|
|
@ -124,17 +157,35 @@ $(document).ready(function () {
|
|||
$('#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');
|
||||
$form_new.submit(payWithStripe_new);
|
||||
|
||||
function payWithStripe_new(e) {
|
||||
e.preventDefault();
|
||||
|
||||
function stripeTokenHandler(token) {
|
||||
// Insert the token ID into the form so it gets submitted to the server
|
||||
$('#id_token').val(token.id);
|
||||
$('#billing-form').submit();
|
||||
submitBillingForm();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -196,10 +247,10 @@ $(document).ready(function () {
|
|||
}
|
||||
});
|
||||
|
||||
$('.credit-card-info .btn.choice-btn').click(function(){
|
||||
var id = this.dataset['id_card'];
|
||||
$('#id_card').val(id);
|
||||
$('#billing-form').submit();
|
||||
$('.credit-card-info .btn.choice-btn').click(function () {
|
||||
var id = this.dataset['id_card'];
|
||||
$('#id_card').val(id);
|
||||
submitBillingForm();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -134,3 +134,15 @@ $(document).ready(function() {
|
|||
$(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>
|
||||
<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;">
|
||||
{% blocktrans %}You are receiving this email because your virutal machine <strong>{{ vm_name }}</strong> has been cancelled.{% endblocktrans %}
|
||||
{% blocktrans %}You are receiving this email because your virtual machine <strong>{{ vm_name }}</strong> has been cancelled.{% endblocktrans %}
|
||||
</p>
|
||||
<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 %}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
{% trans "Virtual Machine Cancellation" %}
|
||||
|
||||
{% blocktrans %}You are receiving this email because your virutal machine {{vm_name}} has been cancelled.{% endblocktrans %}
|
||||
{% blocktrans %}You are receiving this email because your virtual machine {{vm_name}} has been cancelled.{% endblocktrans %}
|
||||
{% blocktrans %}You can always order a new VM by following the link below.{% endblocktrans %}
|
||||
|
||||
{{ base_url }}{% url 'hosting:create_virtual_machine' %}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
{% endif %}
|
||||
</span>
|
||||
</p>
|
||||
{% if order %}
|
||||
{% if order and vm %}
|
||||
<p>
|
||||
<strong>{% trans "Status" %}: </strong>
|
||||
<strong>
|
||||
|
|
@ -93,77 +93,104 @@
|
|||
<hr>
|
||||
<div>
|
||||
<h4>{% trans "Order summary" %}</h4>
|
||||
<p>
|
||||
<strong>{% trans "Product" %}:</strong>
|
||||
{% if vm.name %}
|
||||
{{ vm.name }}
|
||||
{% else %}
|
||||
{{ request.session.template.name }}
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
{% if vm.created_at %}
|
||||
<p>
|
||||
<span>{% trans "Period" %}: </span>
|
||||
<span>
|
||||
<span class="locale_date" data-format="YYYY/MM/DD">{{ vm.created_at|date:'Y-m-d h:i a' }}</span> - <span class="locale_date" data-format="YYYY/MM/DD">{{ subscription_end_date|date:'Y-m-d h:i a' }}</span>
|
||||
</span>
|
||||
</p>
|
||||
{% if vm %}
|
||||
<p>
|
||||
<strong>{% trans "Product" %}:</strong>
|
||||
{% if vm.name %}
|
||||
{{ vm.name }}
|
||||
{% else %}
|
||||
{{ request.session.template.name }}
|
||||
{% endif %}
|
||||
<p>
|
||||
<span>{% trans "Cores" %}: </span>
|
||||
{% if vm.cores %}
|
||||
<strong class="pull-right">{{vm.cores|floatformat}}</strong>
|
||||
{% else %}
|
||||
<strong class="pull-right">{{vm.cpu|floatformat}}</strong>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<span>{% trans "Memory" %}: </span>
|
||||
<strong class="pull-right">{{vm.memory}} GB</strong>
|
||||
</p>
|
||||
<p>
|
||||
<span>{% trans "Disk space" %}: </span>
|
||||
<strong class="pull-right">{{vm.disk_size}} GB</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<hr class="thin-hr">
|
||||
</div>
|
||||
{% if vm.vat > 0 or vm.discount.amount > 0 %}
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="subtotal-price">
|
||||
{% if vm.vat > 0 %}
|
||||
<p>
|
||||
<strong>{% trans "Subtotal" %} </strong>
|
||||
<strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
|
||||
</p>
|
||||
<p>
|
||||
<small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) </small>
|
||||
<strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
|
||||
</p>
|
||||
{% if vm.created_at %}
|
||||
<p>
|
||||
<span>{% trans "Period" %}: </span>
|
||||
<span>
|
||||
<span class="locale_date" data-format="YYYY/MM/DD">{{ vm.created_at|date:'Y-m-d h:i a' }}</span> - <span class="locale_date" data-format="YYYY/MM/DD">{{ subscription_end_date|date:'Y-m-d h:i a' }}</span>
|
||||
</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
<span>{% trans "Cores" %}: </span>
|
||||
{% if vm.cores %}
|
||||
<strong class="pull-right">{{vm.cores|floatformat}}</strong>
|
||||
{% else %}
|
||||
<strong class="pull-right">{{vm.cpu|floatformat}}</strong>
|
||||
{% endif %}
|
||||
{% if vm.discount.amount > 0 %}
|
||||
<p class="text-primary">
|
||||
{%trans "Discount" as discount_name %}
|
||||
<strong>{{ vm.discount.name|default:discount_name }} </strong>
|
||||
<strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</p>
|
||||
<p>
|
||||
<span>{% trans "Memory" %}: </span>
|
||||
<strong class="pull-right">{{vm.memory}} GB</strong>
|
||||
</p>
|
||||
<p>
|
||||
<span>{% trans "Disk space" %}: </span>
|
||||
<strong class="pull-right">{{vm.disk_size}} GB</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<hr class="thin-hr">
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-sm-6">
|
||||
<p class="total-price">
|
||||
<strong>{% trans "Total" %} </strong>
|
||||
<strong class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %} CHF</strong>
|
||||
</p>
|
||||
{% if vm.vat > 0 or vm.discount.amount > 0 %}
|
||||
<div class="col-sm-6">
|
||||
<div class="subtotal-price">
|
||||
{% if vm.vat > 0 %}
|
||||
<p>
|
||||
<strong>{% trans "Subtotal" %} </strong>
|
||||
<strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
|
||||
</p>
|
||||
<p>
|
||||
<small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) </small>
|
||||
<strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if vm.discount.amount > 0 %}
|
||||
<p class="text-primary">
|
||||
{%trans "Discount" as discount_name %}
|
||||
<strong>{{ vm.discount.name|default:discount_name }} </strong>
|
||||
<strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<hr class="thin-hr">
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-sm-6">
|
||||
<p class="total-price">
|
||||
<strong>{% trans "Total" %} </strong>
|
||||
<strong class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %} CHF</strong>
|
||||
</p>
|
||||
</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>
|
||||
<hr class="thin-hr">
|
||||
</div>
|
||||
|
|
@ -229,17 +256,6 @@
|
|||
<script type="text/javascript">
|
||||
{% trans "Some problem encountered. Please try again later." as err_msg %}
|
||||
var create_vm_error_message = '{{err_msg|safe}}';
|
||||
window.onload = function () {
|
||||
var locale_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>
|
||||
{%endblock%}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
{% for order in orders %}
|
||||
<tr>
|
||||
<td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td>
|
||||
<td class="xs-td-bighalf" data-header="{% trans 'Date' %}">{{ order.created_at | date:"M d, Y H:i" }}</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-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|floatformat:2|intcomma }}</td>
|
||||
<td class="text-right last-td">
|
||||
<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>
|
||||
<hr>
|
||||
<form role="form" id="billing-form" method="post" action="" novalidate>
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{% csrf_token %}
|
||||
{% bootstrap_field field show_label=False type='fields' bound_css_class='' %}
|
||||
{% endfor %}
|
||||
<div class="form-group text-right">
|
||||
|
|
|
|||
124
hosting/views.py
124
hosting/views.py
|
|
@ -32,6 +32,7 @@ from stored_messages.api import mark_read
|
|||
from stored_messages.models import Message
|
||||
from stored_messages.settings import stored_messages_settings
|
||||
|
||||
from datacenterlight.cms_models import DCLCalculatorPluginModel
|
||||
from datacenterlight.models import VMTemplate, VMPricing
|
||||
from datacenterlight.utils import create_vm, get_cms_integration
|
||||
from hosting.models import UserCardDetail
|
||||
|
|
@ -59,7 +60,8 @@ from .forms import (
|
|||
)
|
||||
from .mixins import ProcessVMSelectionMixin, HostingContextMixin
|
||||
from .models import (
|
||||
HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail
|
||||
HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail,
|
||||
GenericProduct
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -864,32 +866,20 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
|
|||
raise Http404
|
||||
|
||||
if obj is not None:
|
||||
# invoice for previous order
|
||||
try:
|
||||
vm_detail = VMDetail.objects.get(vm_id=obj.vm_id)
|
||||
context['vm'] = vm_detail.__dict__
|
||||
context['vm']['name'] = '{}-{}'.format(
|
||||
context['vm']['configuration'], context['vm']['vm_id'])
|
||||
price, vat, vat_percent, discount = get_vm_price_with_vat(
|
||||
cpu=context['vm']['cores'],
|
||||
ssd_size=context['vm']['disk_size'],
|
||||
memory=context['vm']['memory'],
|
||||
pricing_name=(obj.vm_pricing.name
|
||||
if obj.vm_pricing else 'default')
|
||||
)
|
||||
context['vm']['vat'] = vat
|
||||
context['vm']['price'] = price
|
||||
context['vm']['discount'] = discount
|
||||
context['vm']['vat_percent'] = vat_percent
|
||||
context['vm']['total_price'] = price + vat - discount['amount']
|
||||
context['subscription_end_date'] = vm_detail.end_date()
|
||||
except VMDetail.DoesNotExist:
|
||||
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
|
||||
logger.debug("Invoice of VM order")
|
||||
try:
|
||||
manager = OpenNebulaManager(
|
||||
email=owner.email, password=owner.password
|
||||
)
|
||||
vm = manager.get_vm(obj.vm_id)
|
||||
context['vm'] = VirtualMachineSerializer(vm).data
|
||||
vm_detail = VMDetail.objects.get(vm_id=obj.vm_id)
|
||||
context['vm'] = vm_detail.__dict__
|
||||
context['vm']['name'] = '{}-{}'.format(
|
||||
context['vm']['configuration'], context['vm']['vm_id'])
|
||||
price, vat, vat_percent, discount = get_vm_price_with_vat(
|
||||
cpu=context['vm']['cores'],
|
||||
ssd_size=context['vm']['disk_size'],
|
||||
|
|
@ -901,23 +891,43 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
|
|||
context['vm']['price'] = price
|
||||
context['vm']['discount'] = discount
|
||||
context['vm']['vat_percent'] = vat_percent
|
||||
context['vm']['total_price'] = (
|
||||
price + vat - discount['amount']
|
||||
)
|
||||
except WrongIdError:
|
||||
messages.error(
|
||||
self.request,
|
||||
_('The VM you are looking for is unavailable at the '
|
||||
'moment. Please contact Data Center Light support.')
|
||||
)
|
||||
self.kwargs['error'] = 'WrongIdError'
|
||||
context['error'] = 'WrongIdError'
|
||||
except ConnectionRefusedError:
|
||||
messages.error(
|
||||
self.request,
|
||||
_('In order to create a VM, you need to create/upload '
|
||||
'your SSH KEY first.')
|
||||
)
|
||||
context['vm']['total_price'] = price + vat - discount['amount']
|
||||
context['subscription_end_date'] = vm_detail.end_date()
|
||||
except VMDetail.DoesNotExist:
|
||||
try:
|
||||
manager = OpenNebulaManager(
|
||||
email=owner.email, password=owner.password
|
||||
)
|
||||
vm = manager.get_vm(obj.vm_id)
|
||||
context['vm'] = VirtualMachineSerializer(vm).data
|
||||
price, vat, vat_percent, discount = get_vm_price_with_vat(
|
||||
cpu=context['vm']['cores'],
|
||||
ssd_size=context['vm']['disk_size'],
|
||||
memory=context['vm']['memory'],
|
||||
pricing_name=(obj.vm_pricing.name
|
||||
if obj.vm_pricing else 'default')
|
||||
)
|
||||
context['vm']['vat'] = vat
|
||||
context['vm']['price'] = price
|
||||
context['vm']['discount'] = discount
|
||||
context['vm']['vat_percent'] = vat_percent
|
||||
context['vm']['total_price'] = (
|
||||
price + vat - discount['amount']
|
||||
)
|
||||
except WrongIdError:
|
||||
messages.error(
|
||||
self.request,
|
||||
_('The VM you are looking for is unavailable at the '
|
||||
'moment. Please contact Data Center Light support.')
|
||||
)
|
||||
self.kwargs['error'] = 'WrongIdError'
|
||||
context['error'] = 'WrongIdError'
|
||||
except ConnectionRefusedError:
|
||||
messages.error(
|
||||
self.request,
|
||||
_('In order to create a VM, you need to create/upload '
|
||||
'your SSH KEY first.')
|
||||
)
|
||||
else:
|
||||
# new order, confirm payment
|
||||
if 'token' in self.request.session:
|
||||
|
|
@ -1191,7 +1201,29 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
|
|||
raise ValidationError(_('Invalid number of cores'))
|
||||
|
||||
def validate_memory(self, value):
|
||||
if (value > 200) or (value < 1):
|
||||
if 'pid' in self.request.POST:
|
||||
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'))
|
||||
|
||||
def validate_storage(self, value):
|
||||
|
|
@ -1211,7 +1243,7 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
|
|||
cores = request.POST.get('cpu')
|
||||
cores_field = forms.IntegerField(validators=[self.validate_cores])
|
||||
memory = request.POST.get('ram')
|
||||
memory_field = forms.IntegerField(validators=[self.validate_memory])
|
||||
memory_field = forms.FloatField(validators=[self.validate_memory])
|
||||
storage = request.POST.get('storage')
|
||||
storage_field = forms.IntegerField(validators=[self.validate_storage])
|
||||
template_id = int(request.POST.get('config'))
|
||||
|
|
@ -1275,7 +1307,7 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
|
|||
'price': price,
|
||||
'vat': vat,
|
||||
'vat_percent': vat_percent,
|
||||
'total_price': price + vat - discount['amount'],
|
||||
'total_price': round(price + vat - discount['amount'], 2),
|
||||
'pricing_name': vm_pricing_name
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue