Merge pull request #622 from pcoder/task/4379/price_without_vat
Task/4379/Price without VAT
This commit is contained in:
commit
74554f1c24
21 changed files with 504 additions and 118 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -42,3 +42,4 @@ secret-key
|
|||
|
||||
# to keep empty dirs
|
||||
!.gitkeep
|
||||
*.orig
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django.contrib import admin
|
||||
from cms.admin.placeholderadmin import PlaceholderAdminMixin
|
||||
from .cms_models import CMSIntegration
|
||||
from .models import VMPricing
|
||||
|
||||
|
||||
class CMSIntegrationAdmin(PlaceholderAdminMixin, admin.ModelAdmin):
|
||||
|
@ -8,3 +9,4 @@ class CMSIntegrationAdmin(PlaceholderAdminMixin, admin.ModelAdmin):
|
|||
|
||||
|
||||
admin.site.register(CMSIntegration, CMSIntegrationAdmin)
|
||||
admin.site.register(VMPricing)
|
||||
|
|
|
@ -6,6 +6,8 @@ from django.utils.safestring import mark_safe
|
|||
from djangocms_text_ckeditor.fields import HTMLField
|
||||
from filer.fields.image import FilerImageField
|
||||
|
||||
from datacenterlight.models import VMPricing
|
||||
|
||||
|
||||
class CMSIntegration(models.Model):
|
||||
name = models.CharField(
|
||||
|
@ -275,3 +277,12 @@ class DCLSectionPromoPluginModel(CMSPlugin):
|
|||
if self.background_image:
|
||||
extra_classes += ' promo-with-bg'
|
||||
return extra_classes
|
||||
|
||||
|
||||
class DCLCustomPricingModel(CMSPlugin):
|
||||
pricing = models.ForeignKey(
|
||||
VMPricing,
|
||||
related_name="dcl_custom_pricing_vm_pricing",
|
||||
help_text='Choose a pricing that will be associated with this '
|
||||
'Calculator'
|
||||
)
|
||||
|
|
|
@ -6,9 +6,9 @@ from .cms_models import (
|
|||
DCLFooterPluginModel, DCLLinkPluginModel, DCLNavbarDropdownPluginModel,
|
||||
DCLSectionIconPluginModel, DCLSectionImagePluginModel,
|
||||
DCLSectionPluginModel, DCLNavbarPluginModel,
|
||||
DCLSectionPromoPluginModel
|
||||
DCLSectionPromoPluginModel, DCLCustomPricingModel
|
||||
)
|
||||
from .models import VMTemplate
|
||||
from .models import VMTemplate, VMPricing
|
||||
|
||||
|
||||
@plugin_pool.register_plugin
|
||||
|
@ -75,13 +75,13 @@ class DCLSectionPromoPlugin(CMSPluginBase):
|
|||
@plugin_pool.register_plugin
|
||||
class DCLCalculatorPlugin(CMSPluginBase):
|
||||
module = "Datacenterlight"
|
||||
name = "DCL Calculator Plugin"
|
||||
name = "DCL Calculator Section Plugin"
|
||||
model = DCLSectionPluginModel
|
||||
render_template = "datacenterlight/cms/calculator.html"
|
||||
cache = False
|
||||
allow_children = True
|
||||
child_classes = [
|
||||
'DCLSectionPromoPlugin', 'UngleichHTMLPlugin'
|
||||
'DCLSectionPromoPlugin', 'UngleichHTMLPlugin', 'DCLCustomPricingPlugin'
|
||||
]
|
||||
|
||||
def render(self, context, instance, placeholder):
|
||||
|
@ -89,15 +89,38 @@ class DCLCalculatorPlugin(CMSPluginBase):
|
|||
context, instance, placeholder
|
||||
)
|
||||
context['templates'] = VMTemplate.objects.all()
|
||||
context['children_to_side'] = []
|
||||
context['children_to_content'] = []
|
||||
pricing_plugin_model = None
|
||||
if instance.child_plugin_instances is not None:
|
||||
context['children_to_content'].extend(
|
||||
instance.child_plugin_instances
|
||||
)
|
||||
for child in instance.child_plugin_instances:
|
||||
if child.__class__.__name__ == 'DCLCustomPricingModel':
|
||||
# The second clause is just to make sure we pick up the
|
||||
# most recent CustomPricing, if more than one is present
|
||||
if (pricing_plugin_model is None or child.pricing_id >
|
||||
pricing_plugin_model.model.pricing_id):
|
||||
pricing_plugin_model = child
|
||||
|
||||
if pricing_plugin_model:
|
||||
context['vm_pricing'] = VMPricing.get_vm_pricing_by_name(
|
||||
name=pricing_plugin_model.pricing.name
|
||||
)
|
||||
else:
|
||||
context['vm_pricing'] = VMPricing.get_default_pricing()
|
||||
|
||||
return context
|
||||
|
||||
|
||||
@plugin_pool.register_plugin
|
||||
class DCLCustomPricingPlugin(CMSPluginBase):
|
||||
module = "Datacenterlight"
|
||||
name = "DCL Custom Pricing Plugin"
|
||||
model = DCLCustomPricingModel
|
||||
render_plugin = False
|
||||
|
||||
|
||||
@plugin_pool.register_plugin
|
||||
class DCLBannerListPlugin(CMSPluginBase):
|
||||
module = "Datacenterlight"
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-03-30 21:29+0000\n"
|
||||
"POT-Creation-Date: 2018-04-17 19:26+0000\n"
|
||||
"PO-Revision-Date: 2018-03-30 23:22+0000\n"
|
||||
"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -72,9 +72,9 @@ msgstr "Data Center Light Account Aktivierung"
|
|||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You can activate your Data Center Light account by clicking <a "
|
||||
"href=\"%(base_url)s%(activation_link)s\" style=\"text-decoration: none; "
|
||||
"color: #4382c8; font-weight: 400;\">here</a>."
|
||||
"You can activate your Data Center Light account by clicking <a href="
|
||||
"\"%(base_url)s%(activation_link)s\" style=\"text-decoration: none; color: "
|
||||
"#4382c8; font-weight: 400;\">here</a>."
|
||||
msgstr ""
|
||||
"Klicke <a href=\"%(base_url)s%(activation_link)s\"style=\"text-decoration: "
|
||||
"none; color: #4382c8; font-weight: 400;\">hier</a> um deinen Data Center "
|
||||
|
@ -97,12 +97,13 @@ msgstr "Deine E-Mail-Adresse"
|
|||
msgid "Password"
|
||||
msgstr "Passwort"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You can reset your password <a href=\"%(base_url)s%(reset_password_url)s\" "
|
||||
"style=\"text-decoration: none; color: #4382c8; font-weight: 400;\">here</a>."
|
||||
msgstr ""
|
||||
"Du kannst dein Passwort <a href=\"%(base_url)s%(reset_password_url)s\" "
|
||||
"style=\"text-decoration: none; color: #4382c8; font-weight: 400;\">hier</a> "
|
||||
"Du kannst dein Passwort <a href=\"%(base_url)s%(reset_password_url)s\" style="
|
||||
"\"text-decoration: none; color: #4382c8; font-weight: 400;\">hier</a> "
|
||||
"zurücksetzen."
|
||||
|
||||
msgid "Your Data Center Light Team"
|
||||
|
@ -160,21 +161,6 @@ msgstr "Weiter"
|
|||
msgid "Home"
|
||||
msgstr "Home"
|
||||
|
||||
msgid "Highlights"
|
||||
msgstr ""
|
||||
|
||||
msgid "Scale out"
|
||||
msgstr "Skalierung"
|
||||
|
||||
msgid "Reliable and light"
|
||||
msgstr "Zuverlässig und leicht"
|
||||
|
||||
msgid "Pricing"
|
||||
msgstr "Preise"
|
||||
|
||||
msgid "Order VM"
|
||||
msgstr "VM bestellen"
|
||||
|
||||
msgid "Contact"
|
||||
msgstr "Kontakt"
|
||||
|
||||
|
@ -184,6 +170,9 @@ msgstr "Nutzungsbedingungen"
|
|||
msgid "Finally, an affordable VM hosting in Switzerland!"
|
||||
msgstr "Endlich: bezahlbares VM Hosting in der Schweiz"
|
||||
|
||||
msgid "Highlights"
|
||||
msgstr ""
|
||||
|
||||
msgid "I want it!"
|
||||
msgstr "Das will ich haben!"
|
||||
|
||||
|
@ -214,6 +203,9 @@ msgstr ""
|
|||
"mit FOSS (Free Open Source Software) arbeitet und wir daher auf "
|
||||
"Lizenzgebühren verzichten können.
"
|
||||
|
||||
msgid "Scale out"
|
||||
msgstr "Skalierung"
|
||||
|
||||
msgid ""
|
||||
"We don't use special hardware. We use commodity hardware: we buy computers "
|
||||
"that you buy. Just many more and put them in a cozy home for computers "
|
||||
|
@ -223,6 +215,9 @@ msgstr ""
|
|||
"erschwingliche Systeme. Bei grösserer Auslastung werden mehr "
|
||||
"Standardkomponenten hinzugekauft und skalieren so das Datencenter."
|
||||
|
||||
msgid "Reliable and light"
|
||||
msgstr "Zuverlässig und leicht"
|
||||
|
||||
msgid ""
|
||||
"Our VMs are located in Switzerland, with reliable power supply and fast "
|
||||
"internet connection. Our VM costs less thanks to our featherlight "
|
||||
|
@ -232,8 +227,7 @@ msgstr ""
|
|||
"Energieversorgung, sowie schneller Internetverbindung ausgestattet. Unser "
|
||||
"Angebot ist aufgrund unserer leichten Infrastruktur überaus kostengünstig."
|
||||
|
||||
msgid ""
|
||||
"Simple and affordable: Try our virtual machine with featherlight price."
|
||||
msgid "Simple and affordable: Try our virtual machine with featherlight price."
|
||||
msgstr ""
|
||||
"Einfach und bezahlbar: Teste nun unsere virtuellen Maschinen mit "
|
||||
"federleichten Preisen."
|
||||
|
@ -314,6 +308,9 @@ msgstr "Gesamt"
|
|||
msgid "including VAT"
|
||||
msgstr "inkl. Mehrwertsteuer"
|
||||
|
||||
msgid "excluding VAT"
|
||||
msgstr "exkl. Mehrwertsteuer"
|
||||
|
||||
msgid "Month"
|
||||
msgstr "Monat"
|
||||
|
||||
|
@ -321,13 +318,13 @@ msgid "Credit Card"
|
|||
msgstr "Kreditkarte"
|
||||
|
||||
msgid ""
|
||||
"Please fill in your credit card information below. We are using <a "
|
||||
"href=\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do "
|
||||
"not store your information in our database."
|
||||
"Please fill in your credit card information below. We are using <a href="
|
||||
"\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do not "
|
||||
"store your information in our database."
|
||||
msgstr ""
|
||||
"Bitte fülle Deine Kreditkarteninformationen unten aus. Wir nutzen <a "
|
||||
"href=\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung "
|
||||
"und speichern keine Informationen in unserer Datenbank."
|
||||
"Bitte fülle Deine Kreditkarteninformationen unten aus. Wir nutzen <a href="
|
||||
"\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und "
|
||||
"speichern keine Informationen in unserer Datenbank."
|
||||
|
||||
msgid ""
|
||||
"You are not making any payment yet. After submitting your card information, "
|
||||
|
@ -383,13 +380,18 @@ msgstr "Bestellungsübersicht"
|
|||
msgid "Product"
|
||||
msgstr "Produkt"
|
||||
|
||||
#, python-format
|
||||
msgid "Subtotal"
|
||||
msgstr "Zwischensumme"
|
||||
|
||||
msgid "VAT"
|
||||
msgstr "Mehrwertsteuer"
|
||||
|
||||
msgid ""
|
||||
"By clicking \"Place order\" this plan will charge your credit card account "
|
||||
"with the fee of %(vm_price)sCHF/month"
|
||||
"with the fee of %(vm_total_price)s CHF/month"
|
||||
msgstr ""
|
||||
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(vm_price)sCHF "
|
||||
"pro Monat belastet"
|
||||
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
|
||||
"%(vm_total_price)s CHF pro Monat belastet"
|
||||
|
||||
msgid "Place order"
|
||||
msgstr "Bestellen"
|
||||
|
@ -459,21 +461,21 @@ msgid ""
|
|||
"community."
|
||||
msgstr ""
|
||||
"Data Center Light ist ein Teil der Free und Opens Source Software (FOSS) "
|
||||
"Bewegung.<br/> Wir sind damit gross geworden, leben damit und glauben "
|
||||
"daran.<br/> Je weiter wir mit unserem Data Center Light vorankommen, desto "
|
||||
"mehr können wir etwas an die FOSS Community zurückgeben."
|
||||
"Bewegung.<br/> Wir sind damit gross geworden, leben damit und glauben daran."
|
||||
"<br/> Je weiter wir mit unserem Data Center Light vorankommen, desto mehr "
|
||||
"können wir etwas an die FOSS Community zurückgeben."
|
||||
|
||||
msgid "We bring the future to you."
|
||||
msgstr "Wir bringen die Zukunft zu dir."
|
||||
|
||||
msgid ""
|
||||
"Data Center Light uses the most modern technologies out there.<br>Your VM "
|
||||
"needs only IPv6. Data Center Light provides<br> transparent two-way "
|
||||
"IPv6/IPv4 translation."
|
||||
"needs only IPv6. Data Center Light provides<br> transparent two-way IPv6/"
|
||||
"IPv4 translation."
|
||||
msgstr ""
|
||||
"Data Center Light verwendet die zur Zeit modernsten Technologien.<br/>Deine "
|
||||
"VM läuft mit IPv6. Data Center Light bietet eine transparente "
|
||||
"IPv6/IPv4-Zweiweglösung."
|
||||
"VM läuft mit IPv6. Data Center Light bietet eine transparente IPv6/IPv4-"
|
||||
"Zweiweglösung."
|
||||
|
||||
msgid ""
|
||||
" No more spinning metal plates! Data Center Light uses only SSDs. We keep "
|
||||
|
@ -497,6 +499,10 @@ msgstr "Ungültige RAM-Grösse"
|
|||
msgid "Invalid storage size"
|
||||
msgstr "Ungültige Speicher-Grösse"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Incorrect pricing name. Please contact support{support_email}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Confirm Order"
|
||||
msgstr "Bestellung Bestätigen"
|
||||
|
||||
|
@ -520,6 +526,12 @@ msgstr ""
|
|||
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
|
||||
"auf sie zugreifen kannst."
|
||||
|
||||
#~ msgid "Pricing"
|
||||
#~ msgstr "Preise"
|
||||
|
||||
#~ msgid "Order VM"
|
||||
#~ msgstr "VM bestellen"
|
||||
|
||||
#~ msgid "Enter name"
|
||||
#~ msgstr "Name"
|
||||
|
||||
|
@ -533,18 +545,19 @@ msgstr ""
|
|||
#~ msgstr "Anfrage verschickt"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Thank you for your subscription! You will receive a confirmation mail from "
|
||||
#~ "our team"
|
||||
#~ "Thank you for your subscription! You will receive a confirmation mail "
|
||||
#~ "from our team"
|
||||
#~ msgstr ""
|
||||
#~ "Vielen dank für Ihre Anmeldung. Sie erhalten in kürze eine Bestätigungsmail "
|
||||
#~ "von unserem Team"
|
||||
#~ "Vielen dank für Ihre Anmeldung. Sie erhalten in kürze eine "
|
||||
#~ "Bestätigungsmail von unserem Team"
|
||||
|
||||
#~ msgid "Thank you for your request."
|
||||
#~ msgstr "Vielen Dank für Deine Anfrage."
|
||||
|
||||
#~ msgid "You are one step away from being our beta tester!"
|
||||
#~ msgstr ""
|
||||
#~ "Sie sind nur noch einen Schritt davon entfernt, unser Beta-Tester zu werden!"
|
||||
#~ "Sie sind nur noch einen Schritt davon entfernt, unser Beta-Tester zu "
|
||||
#~ "werden!"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Currently we are running our tests to make sure everything runs perfectly."
|
||||
|
@ -553,8 +566,8 @@ msgstr ""
|
|||
#~ "sicherzustellen."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "In the meantime, we would like to ask you a little patience<br/> until our "
|
||||
#~ "team contacts you with beta access."
|
||||
#~ "In the meantime, we would like to ask you a little patience<br/> until "
|
||||
#~ "our team contacts you with beta access."
|
||||
#~ msgstr ""
|
||||
#~ "Wir werden dann sobald als möglich Ihren Beta-Zugang erstellen und Sie "
|
||||
#~ "daraufhin kontaktieren.Bis dahin bitten wir Sie um etwas Geduld."
|
||||
|
@ -564,8 +577,8 @@ msgstr ""
|
|||
|
||||
#~ msgid "Thank you for order! Our team will contact you via email"
|
||||
#~ msgstr ""
|
||||
#~ "Vielen Dank für die Bestellung. Unser Team setzt sich sobald wie möglich mit"
|
||||
#~ " Dir via E-Mail in Verbindung."
|
||||
#~ "Vielen Dank für die Bestellung. Unser Team setzt sich sobald wie möglich "
|
||||
#~ "mit Dir via E-Mail in Verbindung."
|
||||
|
||||
#~ msgid "Affordable VM hosting based in Switzerland"
|
||||
#~ msgstr "Bezahlbares VM Hosting in der Schweiz"
|
||||
|
@ -581,18 +594,18 @@ msgstr ""
|
|||
|
||||
#~ msgid ""
|
||||
#~ "Our VMs are hosted in Glarus, Switzerland, and our website is currently "
|
||||
#~ "running in BETA mode. If you want more information that you did not find on "
|
||||
#~ "our website, or if your order is more detailed, or if you encounter any "
|
||||
#~ "technical hiccups, please contact us at support@datacenterlight.ch, our team"
|
||||
#~ " will get in touch with you asap."
|
||||
#~ "running in BETA mode. If you want more information that you did not find "
|
||||
#~ "on our website, or if your order is more detailed, or if you encounter "
|
||||
#~ "any technical hiccups, please contact us at support@datacenterlight.ch, "
|
||||
#~ "our team will get in touch with you asap."
|
||||
#~ msgstr ""
|
||||
#~ "Unsere VMs werden in der Schweiz im Kanton Glarus gehostet und befinden sich"
|
||||
#~ " zur Zeit noch in der BETA-Phase. Möchtest du mehr über uns erfahren und "
|
||||
#~ "hast auf unserer Website nicht genügend Informationen gefunden? Möchtest "
|
||||
#~ "eine detailliertere Bestellung aufgeben? Bist du auf technische Probleme "
|
||||
#~ "gestossen, die du uns mitteilen möchtest? Dann zögere nicht und kontaktiere "
|
||||
#~ "uns unter support@datacenterlight.ch. Unser Team wird sich umgehend um dein "
|
||||
#~ "Anliegen kümmern!"
|
||||
#~ "Unsere VMs werden in der Schweiz im Kanton Glarus gehostet und befinden "
|
||||
#~ "sich zur Zeit noch in der BETA-Phase. Möchtest du mehr über uns erfahren "
|
||||
#~ "und hast auf unserer Website nicht genügend Informationen gefunden? "
|
||||
#~ "Möchtest eine detailliertere Bestellung aufgeben? Bist du auf technische "
|
||||
#~ "Probleme gestossen, die du uns mitteilen möchtest? Dann zögere nicht und "
|
||||
#~ "kontaktiere uns unter support@datacenterlight.ch. Unser Team wird sich "
|
||||
#~ "umgehend um dein Anliegen kümmern!"
|
||||
|
||||
#~ msgid "is not a proper name"
|
||||
#~ msgstr "ist kein gültiger Name"
|
||||
|
@ -610,12 +623,14 @@ msgstr ""
|
|||
#~ "\n"
|
||||
#~ "Hi,\n"
|
||||
#~ "\n"
|
||||
#~ "You can activate your %(dcl_text)s account by clicking here %(base_url)s%(activation_link)s\n"
|
||||
#~ "You can activate your %(dcl_text)s account by clicking here %(base_url)s"
|
||||
#~ "%(activation_link)s\n"
|
||||
#~ msgstr ""
|
||||
#~ "\n"
|
||||
#~ "Hallo,\n"
|
||||
#~ "\n"
|
||||
#~ "Du kannst deinen %(dcl_text)s Account aktivieren, indem du hier klickst %(base_url)s%(activation_link)s\n"
|
||||
#~ "Du kannst deinen %(dcl_text)s Account aktivieren, indem du hier klickst "
|
||||
#~ "%(base_url)s%(activation_link)s\n"
|
||||
|
||||
#~ msgid "Your"
|
||||
#~ msgstr "Dein"
|
||||
|
@ -650,12 +665,14 @@ msgstr ""
|
|||
#~ msgid "I want to have it!"
|
||||
#~ msgstr "Das möchte ich haben!"
|
||||
|
||||
#~ msgid "Reuse existing factory halls intead of building an expensive building."
|
||||
#~ msgid ""
|
||||
#~ "Reuse existing factory halls intead of building an expensive building."
|
||||
#~ msgstr ""
|
||||
#~ "Nachhaltigkeit: Wiederverwendung ehemaliger Fabrikhallen an Stelle der "
|
||||
#~ "Errichtung eines neuen Gebäudes"
|
||||
|
||||
#~ msgid "Being creative, using modern and alternative design for a datacenter."
|
||||
#~ msgid ""
|
||||
#~ "Being creative, using modern and alternative design for a datacenter."
|
||||
#~ msgstr ""
|
||||
#~ "Kreativität: Verwendung eines modernen und alternativen Designs für unser "
|
||||
#~ "Datencenter"
|
||||
|
@ -678,8 +695,8 @@ msgstr ""
|
|||
#~ msgstr "Standort des Datacenters ist in der Schweiz"
|
||||
|
||||
#~ msgid ""
|
||||
#~ " WARNING: We are currently running in BETA mode. We hope you won't encounter"
|
||||
#~ " any hiccups, but if you do, please let us know at "
|
||||
#~ " WARNING: We are currently running in BETA mode. We hope you won't "
|
||||
#~ "encounter any hiccups, but if you do, please let us know at "
|
||||
#~ "support@datacenterlight.ch"
|
||||
#~ msgstr ""
|
||||
#~ " Achtung: Wir befinden uns zurzeit im Beta-Release. Wir hoffen, dass Sie "
|
||||
|
@ -693,8 +710,8 @@ msgstr ""
|
|||
#~ msgstr "Unser Versprechen"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Instead of creating an expensive SLA for availability, we promise that we do"
|
||||
#~ " our best to run things as smooth as possible."
|
||||
#~ "Instead of creating an expensive SLA for availability, we promise that we "
|
||||
#~ "do our best to run things as smooth as possible."
|
||||
#~ msgstr ""
|
||||
#~ "Anstatt eines SLAs (Service Levle Agreements) zu vereinbaren,setzen wir "
|
||||
#~ "unsere persönliche Arbeitskraft ein, um Ihnen ein sorgenfreiesHosting zu "
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
|
||||
from datacenterlight.models import VMPricing
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Creates default VMPricing object'''
|
||||
DEFAULT_VMPRICING_NAME = 'default'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.create_default_vm_pricing()
|
||||
|
||||
def create_default_vm_pricing(self):
|
||||
obj, created = VMPricing.objects.get_or_create(
|
||||
name=self.DEFAULT_VMPRICING_NAME,
|
||||
defaults={
|
||||
"vat_inclusive": True,
|
||||
"cores_unit_price": 5,
|
||||
"ram_unit_price": 2,
|
||||
"ssd_unit_price": 0.6,
|
||||
"hdd_unit_price": 0.01
|
||||
}
|
||||
)
|
||||
|
||||
if created:
|
||||
print(
|
||||
'Successfully created {} VMPricing object'.format(
|
||||
self.DEFAULT_VMPRICING_NAME
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(
|
||||
'{} VMPricing exists already.'.format(
|
||||
self.DEFAULT_VMPRICING_NAME
|
||||
)
|
||||
)
|
45
datacenterlight/migrations/0019_auto_20180415_2236.py
Normal file
45
datacenterlight/migrations/0019_auto_20180415_2236.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2018-04-15 22:36
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cms', '0014_auto_20160404_1908'),
|
||||
('datacenterlight', '0018_auto_20180403_1930'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DCLCustomPricingModel',
|
||||
fields=[
|
||||
('cmsplugin_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cms.CMSPlugin')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('cms.cmsplugin',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VMPricing',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, unique=True)),
|
||||
('vat_inclusive', models.BooleanField(default=True)),
|
||||
('vat_percentage', models.DecimalField(blank=True, decimal_places=5, default=0, max_digits=7)),
|
||||
('cores_unit_price', models.DecimalField(decimal_places=5, default=0, max_digits=7)),
|
||||
('ram_unit_price', models.DecimalField(decimal_places=5, default=0, max_digits=7)),
|
||||
('ssd_unit_price', models.DecimalField(decimal_places=5, default=0, max_digits=7)),
|
||||
('hdd_unit_price', models.DecimalField(decimal_places=6, default=0, max_digits=7)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dclcustompricingmodel',
|
||||
name='pricing',
|
||||
field=models.ForeignKey(help_text='Choose a pricing that will be associated with this Calculator', on_delete=django.db.models.deletion.CASCADE, related_name='dcl_custom_pricing_vm_pricing', to='datacenterlight.VMPricing'),
|
||||
),
|
||||
]
|
|
@ -1,5 +1,9 @@
|
|||
import logging
|
||||
|
||||
from django.db import models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VMTemplate(models.Model):
|
||||
name = models.CharField(max_length=50)
|
||||
|
@ -12,6 +16,59 @@ class VMTemplate(models.Model):
|
|||
return vm_template
|
||||
|
||||
|
||||
class VMPricing(models.Model):
|
||||
name = models.CharField(max_length=255, unique=True)
|
||||
vat_inclusive = models.BooleanField(default=True)
|
||||
vat_percentage = models.DecimalField(
|
||||
max_digits=7, decimal_places=5, blank=True, default=0
|
||||
)
|
||||
cores_unit_price = models.DecimalField(
|
||||
max_digits=7, decimal_places=5, default=0
|
||||
)
|
||||
ram_unit_price = models.DecimalField(
|
||||
max_digits=7, decimal_places=5, default=0
|
||||
)
|
||||
ssd_unit_price = models.DecimalField(
|
||||
max_digits=7, decimal_places=5, default=0
|
||||
)
|
||||
hdd_unit_price = models.DecimalField(
|
||||
max_digits=7, decimal_places=6, default=0
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name + ' => ' + ' - '.join([
|
||||
'{}/Core'.format(self.cores_unit_price.normalize()),
|
||||
'{}/GB RAM'.format(self.ram_unit_price.normalize()),
|
||||
'{}/GB SSD'.format(self.ssd_unit_price.normalize()),
|
||||
'{}/GB HDD'.format(self.hdd_unit_price.normalize()),
|
||||
'{}% VAT'.format(self.vat_percentage.normalize())
|
||||
if not self.vat_inclusive else 'VAT-Incl', ]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_vm_pricing_by_name(cls, name):
|
||||
try:
|
||||
pricing = VMPricing.objects.get(name=name)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Error getting VMPricing with name {name}. "
|
||||
"Details: {details}. Attempting to return default"
|
||||
"pricing.".format(name=name, details=str(e))
|
||||
)
|
||||
pricing = VMPricing.get_default_pricing()
|
||||
return pricing
|
||||
|
||||
@classmethod
|
||||
def get_default_pricing(cls):
|
||||
""" Returns the default pricing or None """
|
||||
try:
|
||||
default_pricing = VMPricing.objects.get(name='default')
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
default_pricing = None
|
||||
return default_pricing
|
||||
|
||||
|
||||
class StripePlan(models.Model):
|
||||
"""
|
||||
A model to store Data Center Light's created Stripe plans
|
||||
|
|
|
@ -171,7 +171,18 @@
|
|||
}
|
||||
|
||||
function _calcPricing() {
|
||||
var total = (cardPricing['cpu'].value * 5) + (2 * cardPricing['ram'].value) + (0.6 * cardPricing['storage'].value);
|
||||
if(typeof window.coresUnitPrice === 'undefined'){
|
||||
window.coresUnitPrice = 5;
|
||||
}
|
||||
if(typeof window.ramUnitPrice === 'undefined'){
|
||||
window.coresUnitPrice = 2;
|
||||
}
|
||||
if(typeof window.ssdUnitPrice === 'undefined'){
|
||||
window.ssdUnitPrice = 0.6;
|
||||
}
|
||||
var total = (cardPricing['cpu'].value * window.coresUnitPrice) +
|
||||
(cardPricing['ram'].value * window.ramUnitPrice) +
|
||||
(cardPricing['storage'].value * window.ssdUnitPrice);
|
||||
total = parseFloat(total.toFixed(2));
|
||||
$("#total").text(total);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ from utils.forms import UserBillingAddressForm
|
|||
from utils.mailer import BaseEmail
|
||||
from utils.models import BillingAddress
|
||||
|
||||
from .models import VMPricing
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
|
||||
|
||||
|
@ -56,7 +58,8 @@ def create_vm_task(self, vm_template_id, user, specs, template,
|
|||
"Running create_vm_task on {}".format(current_task.request.hostname))
|
||||
vm_id = None
|
||||
try:
|
||||
final_price = specs.get('price')
|
||||
final_price = (specs.get('total_price') if 'total_price' in specs
|
||||
else specs.get('price'))
|
||||
billing_address = BillingAddress(
|
||||
cardholder_name=billing_address_data['cardholder_name'],
|
||||
street_address=billing_address_data['street_address'],
|
||||
|
@ -94,17 +97,22 @@ def create_vm_task(self, vm_template_id, user, specs, template,
|
|||
if vm_id is None:
|
||||
raise Exception("Could not create VM")
|
||||
|
||||
vm_pricing = VMPricing.get_vm_pricing_by_name(
|
||||
name=specs['pricing_name']
|
||||
) if 'pricing_name' in specs else VMPricing.get_default_pricing()
|
||||
# Create a Hosting Order
|
||||
order = HostingOrder.create(
|
||||
price=final_price,
|
||||
vm_id=vm_id,
|
||||
customer=customer,
|
||||
billing_address=billing_address
|
||||
billing_address=billing_address,
|
||||
vm_pricing=vm_pricing
|
||||
)
|
||||
|
||||
# Create a Hosting Bill
|
||||
HostingBill.create(
|
||||
customer=customer, billing_address=billing_address)
|
||||
customer=customer, billing_address=billing_address
|
||||
)
|
||||
|
||||
# Create Billing Address for User if he does not have one
|
||||
if not customer.user.billing_addresses.count():
|
||||
|
@ -130,12 +138,16 @@ def create_vm_task(self, vm_template_id, user, specs, template,
|
|||
'cores': specs.get('cpu'),
|
||||
'memory': specs.get('memory'),
|
||||
'storage': specs.get('disk_size'),
|
||||
'price': specs.get('price'),
|
||||
'price': final_price,
|
||||
'template': template.get('name'),
|
||||
'vm_name': vm.get('name'),
|
||||
'vm_id': vm['vm_id'],
|
||||
'order_id': order.id
|
||||
}
|
||||
if 'pricing_name' in specs:
|
||||
context['pricing'] = str(VMPricing.get_vm_pricing_by_name(
|
||||
name=specs['pricing_name']
|
||||
))
|
||||
email_data = {
|
||||
'subject': settings.DCL_TEXT + " Order from %s" % context['email'],
|
||||
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
|
||||
|
|
|
@ -1,4 +1,16 @@
|
|||
{% load staticfiles i18n%}
|
||||
|
||||
{% if vm_pricing %}
|
||||
<script type="application/javascript">
|
||||
window.vat_inclusive = {% if vm_pricing.vat_inclusive %}true{% else %}false{% endif%};
|
||||
window.vat_percentage = {{vm_pricing.vat_percentage|default:0}};
|
||||
window.coresUnitPrice = {{vm_pricing.cores_unit_price|default:0}};
|
||||
window.ramUnitPrice = {{vm_pricing.ram_unit_price|default:0}};
|
||||
window.ssdUnitPrice = {{vm_pricing.ssd_unit_price|default:0}};
|
||||
window.hddUnitPrice = {{vm_pricing.hdd_unit_price|default:0}};
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<form id="order_form" method="POST" action="{% url 'datacenterlight:index' %}" data-toggle="validator" role="form">
|
||||
{% csrf_token %}
|
||||
<div class="title">
|
||||
|
@ -7,9 +19,11 @@
|
|||
<div class="price">
|
||||
<span id="total">15</span>
|
||||
<span>CHF/{% trans "month" %}</span>
|
||||
{% if vm_pricing.vat_inclusive %}
|
||||
<div class="price-text">
|
||||
<p>{% trans "VAT included" %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="descriptions">
|
||||
<div class="description form-group">
|
||||
|
@ -78,5 +92,6 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="pricing_name" value="{% if vm_pricing.name %}{{vm_pricing.name}}{% else %}unknown{% endif%}"></input>
|
||||
<input type="submit" class="btn btn-primary disabled" value="{% trans 'Continue' %}"></input>
|
||||
</form>
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
<hr>
|
||||
<p>{% trans "Configuration"%} <strong class="pull-right">{{request.session.template.name}}</strong></p>
|
||||
<hr>
|
||||
<p class="last-p"><strong>{%trans "Total" %}</strong> <small>({%trans "including VAT" %})</small> <strong class="pull-right">{{request.session.specs.price|intcomma}} CHF/{% trans "Month" %}</strong></p>
|
||||
<p class="last-p"><strong>{%trans "Total" %}</strong> <small>({% if vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %})</small> <strong class="pull-right">{{request.session.specs.price|intcomma}} CHF/{% trans "Month" %}</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -65,9 +65,19 @@
|
|||
<span>{% trans "Disk space" %}: </span>
|
||||
<span class="pull-right">{{vm.disk_size|intcomma}} GB</span>
|
||||
</p>
|
||||
{% if vm.vat > 0 %}
|
||||
<p>
|
||||
<span>{% trans "Total" %}</span>
|
||||
<span class="pull-right">{{vm.price|intcomma}} CHF</span>
|
||||
<strong>{% trans "Subtotal" %}: </strong>
|
||||
<span class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%): </span>
|
||||
<span class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
<strong>{% trans "Total" %}</strong>
|
||||
<span class="pull-right">{{vm.total_price|floatformat:2|intcomma}} CHF</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -78,7 +88,7 @@
|
|||
{% csrf_token %}
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div class="dcl-place-order-text">{% blocktrans with vm_price=request.session.specs.price %}By clicking "Place order" this plan will charge your credit card account with the fee of {{ vm_price }}CHF/month{% endblocktrans %}.</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 the fee of {{vm_total_price}} CHF/month{% endblocktrans %}.</div>
|
||||
</div>
|
||||
<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">
|
||||
|
|
|
@ -19,11 +19,11 @@ from hosting.models import HostingOrder
|
|||
from membership.models import CustomUser, StripeCustomer
|
||||
from opennebula_api.serializers import VMTemplateSerializer
|
||||
from utils.forms import BillingAddressForm, BillingAddressFormSignup
|
||||
from utils.hosting_utils import get_vm_price
|
||||
from utils.hosting_utils import get_vm_price_with_vat
|
||||
from utils.stripe_utils import StripeUtils
|
||||
from utils.tasks import send_plain_email_task
|
||||
from .forms import ContactForm
|
||||
from .models import VMTemplate
|
||||
from .models import VMTemplate, VMPricing
|
||||
from .utils import get_cms_integration
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -93,7 +93,8 @@ class IndexView(CreateView):
|
|||
|
||||
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
|
||||
def get(self, request, *args, **kwargs):
|
||||
for session_var in ['specs', 'user', 'billing_address_data']:
|
||||
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'))
|
||||
|
@ -106,12 +107,30 @@ class IndexView(CreateView):
|
|||
storage = request.POST.get('storage')
|
||||
storage_field = forms.IntegerField(validators=[self.validate_storage])
|
||||
template_id = int(request.POST.get('config'))
|
||||
pricing_name = request.POST.get('pricing_name')
|
||||
vm_pricing = VMPricing.get_vm_pricing_by_name(pricing_name)
|
||||
|
||||
template = VMTemplate.objects.filter(
|
||||
opennebula_vm_template_id=template_id
|
||||
).first()
|
||||
template_data = VMTemplateSerializer(template).data
|
||||
referer_url = request.META['HTTP_REFERER']
|
||||
|
||||
if vm_pricing is None:
|
||||
vm_pricing_name_msg = _(
|
||||
"Incorrect pricing name. Please contact support"
|
||||
"{support_email}".format(
|
||||
support_email=settings.DCL_SUPPORT_FROM_ADDRESS
|
||||
)
|
||||
)
|
||||
messages.add_message(
|
||||
self.request, messages.ERROR, vm_pricing_name_msg,
|
||||
extra_tags='pricing'
|
||||
)
|
||||
return HttpResponseRedirect(referer_url + "#order_form")
|
||||
else:
|
||||
vm_pricing_name = vm_pricing.name
|
||||
|
||||
try:
|
||||
cores = cores_field.clean(cores)
|
||||
except ValidationError as err:
|
||||
|
@ -139,14 +158,21 @@ class IndexView(CreateView):
|
|||
)
|
||||
return HttpResponseRedirect(referer_url + "#order_form")
|
||||
|
||||
amount_to_be_charged = get_vm_price(
|
||||
cpu=cores, memory=memory, disk_size=storage
|
||||
price, vat, vat_percent = get_vm_price_with_vat(
|
||||
cpu=cores,
|
||||
memory=memory,
|
||||
ssd_size=storage,
|
||||
pricing_name=vm_pricing_name
|
||||
)
|
||||
specs = {
|
||||
'cpu': cores,
|
||||
'memory': memory,
|
||||
'disk_size': storage,
|
||||
'price': amount_to_be_charged
|
||||
'price': price,
|
||||
'vat': vat,
|
||||
'vat_percent': vat_percent,
|
||||
'total_price': price + vat,
|
||||
'pricing_name': vm_pricing_name
|
||||
}
|
||||
request.session['specs'] = specs
|
||||
request.session['template'] = template_data
|
||||
|
@ -220,7 +246,10 @@ class PaymentOrderView(FormView):
|
|||
'site_url': reverse('datacenterlight:index'),
|
||||
'login_form': HostingUserLoginForm(prefix='login_form'),
|
||||
'billing_address_form': billing_address_form,
|
||||
'cms_integration': get_cms_integration('default')
|
||||
'cms_integration': get_cms_integration('default'),
|
||||
'vm_pricing': VMPricing.get_vm_pricing_by_name(
|
||||
self.request.session['specs']['pricing_name']
|
||||
)
|
||||
})
|
||||
return context
|
||||
|
||||
|
@ -393,7 +422,7 @@ class OrderConfirmationView(DetailView):
|
|||
cpu = specs.get('cpu')
|
||||
memory = specs.get('memory')
|
||||
disk_size = specs.get('disk_size')
|
||||
amount_to_be_charged = specs.get('price')
|
||||
amount_to_be_charged = specs.get('total_price')
|
||||
plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
|
||||
memory=memory,
|
||||
disk_size=disk_size)
|
||||
|
@ -489,7 +518,7 @@ class OrderConfirmationView(DetailView):
|
|||
stripe_subscription_obj.id, card_details_dict)
|
||||
for session_var in ['specs', 'template', 'billing_address',
|
||||
'billing_address_data',
|
||||
'token', 'customer']:
|
||||
'token', 'customer', 'pricing_name']:
|
||||
if session_var in request.session:
|
||||
del request.session[session_var]
|
||||
|
||||
|
|
23
hosting/migrations/0044_hostingorder_vm_pricing.py
Normal file
23
hosting/migrations/0044_hostingorder_vm_pricing.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2018-04-16 00:22
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('datacenterlight', '0019_auto_20180415_2236'),
|
||||
('hosting', '0043_vmdetail'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='hostingorder',
|
||||
name='vm_pricing',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='datacenterlight.VMPricing'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
|
@ -6,6 +6,8 @@ from django.db import models
|
|||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
from datacenterlight.models import VMPricing
|
||||
from membership.models import StripeCustomer, CustomUser
|
||||
from utils.models import BillingAddress
|
||||
from utils.mixins import AssignPermissionsMixin
|
||||
|
@ -53,6 +55,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
|
|||
stripe_charge_id = models.CharField(max_length=100, null=True)
|
||||
price = models.FloatField()
|
||||
subscription_id = models.CharField(max_length=100, null=True)
|
||||
vm_pricing = models.ForeignKey(VMPricing)
|
||||
|
||||
permissions = ('view_hostingorder',)
|
||||
|
||||
|
@ -70,12 +73,13 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
|
|||
|
||||
@classmethod
|
||||
def create(cls, price=None, vm_id=None, customer=None,
|
||||
billing_address=None):
|
||||
billing_address=None, vm_pricing=None):
|
||||
instance = cls.objects.create(
|
||||
price=price,
|
||||
vm_id=vm_id,
|
||||
customer=customer,
|
||||
billing_address=billing_address
|
||||
billing_address=billing_address,
|
||||
vm_pricing=vm_pricing
|
||||
)
|
||||
instance.assign_permissions(customer.user)
|
||||
return instance
|
||||
|
|
|
@ -127,9 +127,19 @@
|
|||
<span>{% trans "Disk space" %}: </span>
|
||||
<span class="pull-right">{{vm.disk_size}} GB</span>
|
||||
</p>
|
||||
{% if vm.vat > 0 %}
|
||||
<p>
|
||||
<span>{% trans "Total" %}</span>
|
||||
<span class="pull-right">{{vm.price|intcomma}} CHF</span>
|
||||
<strong>{% trans "Subtotal" %}: </strong>
|
||||
<span class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</span>
|
||||
</p>
|
||||
<p>
|
||||
<span>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%): </span>
|
||||
<span class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
<p>
|
||||
<strong>{% trans "Total" %}</strong>
|
||||
<span class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %} CHF</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<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-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|intcomma }}</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>
|
||||
</td>
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
<h2 class="vm-detail-title">{% trans "Billing" %} <img src="{% static 'hosting/img/billing.svg' %}" class="un-icon"></h2>
|
||||
<div class="vm-vmid">
|
||||
<div class="vm-item-subtitle">{% trans "Current Pricing" %}</div>
|
||||
<div class="vm-item-lg">{{virtual_machine.price|floatformat|intcomma}} CHF/{% trans "Month" %}</div>
|
||||
<div class="vm-item-lg">{{order.price|floatformat:2|intcomma}} CHF/{% trans "Month" %}</div>
|
||||
<a class="btn btn-vm-invoice" href="{% url 'hosting:orders' order.pk %}">{% trans "See Invoice" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -42,7 +42,7 @@ from utils.forms import (
|
|||
BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm,
|
||||
ResendActivationEmailForm
|
||||
)
|
||||
from utils.hosting_utils import get_vm_price
|
||||
from utils.hosting_utils import get_vm_price, get_vm_price_with_vat
|
||||
from utils.mailer import BaseEmail
|
||||
from utils.stripe_utils import StripeUtils
|
||||
from utils.tasks import send_plain_email_task
|
||||
|
@ -749,11 +749,17 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
|
|||
context['vm'] = vm_detail.__dict__
|
||||
context['vm']['name'] = '{}-{}'.format(
|
||||
context['vm']['configuration'], context['vm']['vm_id'])
|
||||
context['vm']['price'] = get_vm_price(
|
||||
price, vat, vat_percent = get_vm_price_with_vat(
|
||||
cpu=context['vm']['cores'],
|
||||
disk_size=context['vm']['disk_size'],
|
||||
memory=context['vm']['memory']
|
||||
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']['vat_percent'] = vat_percent
|
||||
context['vm']['total_price'] = price + vat
|
||||
context['subscription_end_date'] = vm_detail.end_date()
|
||||
except VMDetail.DoesNotExist:
|
||||
try:
|
||||
|
@ -762,6 +768,17 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
|
|||
)
|
||||
vm = manager.get_vm(obj.vm_id)
|
||||
context['vm'] = VirtualMachineSerializer(vm).data
|
||||
price, vat, vat_percent = 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']['vat_percent'] = vat_percent
|
||||
context['vm']['total_price'] = price + vat
|
||||
except WrongIdError:
|
||||
messages.error(
|
||||
self.request,
|
||||
|
@ -1100,7 +1117,8 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
|||
context = {
|
||||
'virtual_machine': serializer.data,
|
||||
'order': HostingOrder.objects.get(
|
||||
vm_id=serializer.data['vm_id'])
|
||||
vm_id=serializer.data['vm_id']
|
||||
)
|
||||
}
|
||||
except Exception as ex:
|
||||
logger.debug("Exception generated {}".format(str(ex)))
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import decimal
|
||||
import logging
|
||||
from oca.pool import WrongIdError
|
||||
|
||||
from datacenterlight.models import VMPricing
|
||||
from hosting.models import UserHostingKey, VMDetail
|
||||
from opennebula_api.serializers import VirtualMachineSerializer
|
||||
|
||||
|
@ -49,14 +51,74 @@ def get_or_create_vm_detail(user, manager, vm_id):
|
|||
return vm_detail_obj
|
||||
|
||||
|
||||
def get_vm_price(cpu, memory, disk_size):
|
||||
def get_vm_price(cpu, memory, disk_size, hdd_size=0, pricing_name='default'):
|
||||
"""
|
||||
A helper function that computes price of a VM from given cpu, ram and
|
||||
ssd parameters
|
||||
|
||||
:param cpu: Number of cores of the VM
|
||||
:param memory: RAM of the VM
|
||||
:param disk_size: Disk space of the VM
|
||||
:param disk_size: Disk space of the VM (SSD)
|
||||
:param hdd_size: The HDD size
|
||||
:param pricing_name: The pricing name to be used
|
||||
:return: The price of the VM
|
||||
"""
|
||||
return (cpu * 5) + (memory * 2) + (disk_size * 0.6)
|
||||
try:
|
||||
pricing = VMPricing.objects.get(name=pricing_name)
|
||||
except Exception as ex:
|
||||
logger.error(
|
||||
"Error getting VMPricing object for {pricing_name}."
|
||||
"Details: {details}".format(
|
||||
pricing_name=pricing_name, details=str(ex)
|
||||
)
|
||||
)
|
||||
return None
|
||||
price = ((decimal.Decimal(cpu) * pricing.cores_unit_price) +
|
||||
(decimal.Decimal(memory) * pricing.ram_unit_price) +
|
||||
(decimal.Decimal(disk_size) * pricing.ssd_unit_price) +
|
||||
(decimal.Decimal(hdd_size) * pricing.hdd_unit_price))
|
||||
cents = decimal.Decimal('.01')
|
||||
price = price.quantize(cents, decimal.ROUND_HALF_UP)
|
||||
return float(price)
|
||||
|
||||
|
||||
def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0,
|
||||
pricing_name='default'):
|
||||
"""
|
||||
A helper function that computes price of a VM from given cpu, ram and
|
||||
ssd, hdd and the pricing parameters
|
||||
|
||||
:param cpu: Number of cores of the VM
|
||||
:param memory: RAM of the VM
|
||||
:param ssd_size: Disk space of the VM (SSD)
|
||||
:param hdd_size: The HDD size
|
||||
:param pricing_name: The pricing name to be used
|
||||
:return: The a tuple containing the price of the VM, the VAT and the
|
||||
VAT percentage
|
||||
"""
|
||||
try:
|
||||
pricing = VMPricing.objects.get(name=pricing_name)
|
||||
except Exception as ex:
|
||||
logger.error(
|
||||
"Error getting VMPricing object for {pricing_name}."
|
||||
"Details: {details}".format(
|
||||
pricing_name=pricing_name, details=str(ex)
|
||||
)
|
||||
)
|
||||
return None
|
||||
|
||||
price = ((decimal.Decimal(cpu) * pricing.cores_unit_price) +
|
||||
(decimal.Decimal(memory) * pricing.ram_unit_price) +
|
||||
(decimal.Decimal(ssd_size) * pricing.ssd_unit_price) +
|
||||
(decimal.Decimal(hdd_size) * pricing.hdd_unit_price))
|
||||
if pricing.vat_inclusive:
|
||||
vat = decimal.Decimal(0)
|
||||
vat_percent = decimal.Decimal(0)
|
||||
else:
|
||||
vat = price * pricing.vat_percentage * decimal.Decimal(0.01)
|
||||
vat_percent = pricing.vat_percentage
|
||||
|
||||
cents = decimal.Decimal('.01')
|
||||
price = price.quantize(cents, decimal.ROUND_HALF_UP)
|
||||
vat = vat.quantize(cents, decimal.ROUND_HALF_UP)
|
||||
return float(price), float(vat), float(vat_percent)
|
||||
|
|
Loading…
Reference in a new issue