merge master

This commit is contained in:
Arvind Tiwari 2018-04-20 20:30:45 +05:30
commit 275ea47e41
32 changed files with 530 additions and 125 deletions

1
.gitignore vendored
View file

@ -42,3 +42,4 @@ secret-key
# to keep empty dirs # to keep empty dirs
!.gitkeep !.gitkeep
*.orig

View file

@ -1,3 +1,7 @@
next:
* bgfix: [all] Make /blog available on all domains
* #4367: [dcl] email logo resolution fix
* #4376: [cms] dcl promo section plugin link color changed to brighter shade
1.6.5: 2018-04-08 1.6.5: 2018-04-08
* #4396: [ungleich] add favicon to ungleich blog * #4396: [ungleich] add favicon to ungleich blog
* #4327: [dcl] fix navbar logo repeat * #4327: [dcl] fix navbar logo repeat

View file

@ -2,6 +2,7 @@ from django.contrib import admin
from cms.admin.placeholderadmin import PlaceholderAdminMixin from cms.admin.placeholderadmin import PlaceholderAdminMixin
from cms.extensions import PageExtensionAdmin from cms.extensions import PageExtensionAdmin
from .cms_models import CMSIntegration, CMSFaviconExtension from .cms_models import CMSIntegration, CMSFaviconExtension
from .models import VMPricing
class CMSIntegrationAdmin(PlaceholderAdminMixin, admin.ModelAdmin): class CMSIntegrationAdmin(PlaceholderAdminMixin, admin.ModelAdmin):
@ -14,3 +15,4 @@ class CMSFaviconExtensionAdmin(PageExtensionAdmin):
admin.site.register(CMSIntegration, CMSIntegrationAdmin) admin.site.register(CMSIntegration, CMSIntegrationAdmin)
admin.site.register(CMSFaviconExtension, CMSFaviconExtensionAdmin) admin.site.register(CMSFaviconExtension, CMSFaviconExtensionAdmin)
admin.site.register(VMPricing)

View file

@ -9,6 +9,8 @@ from djangocms_text_ckeditor.fields import HTMLField
from filer.fields.file import FilerFileField from filer.fields.file import FilerFileField
from filer.fields.image import FilerImageField from filer.fields.image import FilerImageField
from datacenterlight.models import VMPricing
class CMSIntegration(models.Model): class CMSIntegration(models.Model):
name = models.CharField( name = models.CharField(
@ -284,3 +286,12 @@ class DCLSectionPromoPluginModel(CMSPlugin):
if self.background_image: if self.background_image:
extra_classes += ' promo-with-bg' extra_classes += ' promo-with-bg'
return extra_classes 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'
)

View file

@ -6,9 +6,9 @@ from .cms_models import (
DCLFooterPluginModel, DCLLinkPluginModel, DCLNavbarDropdownPluginModel, DCLFooterPluginModel, DCLLinkPluginModel, DCLNavbarDropdownPluginModel,
DCLSectionIconPluginModel, DCLSectionImagePluginModel, DCLSectionIconPluginModel, DCLSectionImagePluginModel,
DCLSectionPluginModel, DCLNavbarPluginModel, DCLSectionPluginModel, DCLNavbarPluginModel,
DCLSectionPromoPluginModel DCLSectionPromoPluginModel, DCLCustomPricingModel
) )
from .models import VMTemplate from .models import VMTemplate, VMPricing
@plugin_pool.register_plugin @plugin_pool.register_plugin
@ -75,13 +75,13 @@ class DCLSectionPromoPlugin(CMSPluginBase):
@plugin_pool.register_plugin @plugin_pool.register_plugin
class DCLCalculatorPlugin(CMSPluginBase): class DCLCalculatorPlugin(CMSPluginBase):
module = "Datacenterlight" module = "Datacenterlight"
name = "DCL Calculator Plugin" name = "DCL Calculator Section Plugin"
model = DCLSectionPluginModel model = DCLSectionPluginModel
render_template = "datacenterlight/cms/calculator.html" render_template = "datacenterlight/cms/calculator.html"
cache = False cache = False
allow_children = True allow_children = True
child_classes = [ child_classes = [
'DCLSectionPromoPlugin', 'UngleichHTMLPlugin' 'DCLSectionPromoPlugin', 'UngleichHTMLPlugin', 'DCLCustomPricingPlugin'
] ]
def render(self, context, instance, placeholder): def render(self, context, instance, placeholder):
@ -89,15 +89,38 @@ class DCLCalculatorPlugin(CMSPluginBase):
context, instance, placeholder context, instance, placeholder
) )
context['templates'] = VMTemplate.objects.all() context['templates'] = VMTemplate.objects.all()
context['children_to_side'] = []
context['children_to_content'] = [] context['children_to_content'] = []
pricing_plugin_model = None
if instance.child_plugin_instances is not None: if instance.child_plugin_instances is not None:
context['children_to_content'].extend( context['children_to_content'].extend(
instance.child_plugin_instances 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 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 @plugin_pool.register_plugin
class DCLBannerListPlugin(CMSPluginBase): class DCLBannerListPlugin(CMSPluginBase):
module = "Datacenterlight" module = "Datacenterlight"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 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" "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"
@ -72,9 +72,9 @@ msgstr "Data Center Light Account Aktivierung"
#, python-format #, python-format
msgid "" msgid ""
"You can activate your Data Center Light account by clicking <a " "You can activate your Data Center Light account by clicking <a href="
"href=\"%(base_url)s%(activation_link)s\" style=\"text-decoration: none; " "\"%(base_url)s%(activation_link)s\" style=\"text-decoration: none; color: "
"color: #4382c8; font-weight: 400;\">here</a>." "#4382c8; font-weight: 400;\">here</a>."
msgstr "" msgstr ""
"Klicke <a href=\"%(base_url)s%(activation_link)s\"style=\"text-decoration: " "Klicke <a href=\"%(base_url)s%(activation_link)s\"style=\"text-decoration: "
"none; color: #4382c8; font-weight: 400;\">hier</a> um deinen Data Center " "none; color: #4382c8; font-weight: 400;\">hier</a> um deinen Data Center "
@ -97,12 +97,13 @@ msgstr "Deine E-Mail-Adresse"
msgid "Password" msgid "Password"
msgstr "Passwort" msgstr "Passwort"
#, python-format
msgid "" msgid ""
"You can reset your password <a href=\"%(base_url)s%(reset_password_url)s\" " "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>." "style=\"text-decoration: none; color: #4382c8; font-weight: 400;\">here</a>."
msgstr "" msgstr ""
"Du kannst dein Passwort <a href=\"%(base_url)s%(reset_password_url)s\" " "Du kannst dein Passwort <a href=\"%(base_url)s%(reset_password_url)s\" style="
"style=\"text-decoration: none; color: #4382c8; font-weight: 400;\">hier</a> " "\"text-decoration: none; color: #4382c8; font-weight: 400;\">hier</a> "
"zurücksetzen." "zurücksetzen."
msgid "Your Data Center Light Team" msgid "Your Data Center Light Team"
@ -160,21 +161,6 @@ msgstr "Weiter"
msgid "Home" msgid "Home"
msgstr "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" msgid "Contact"
msgstr "Kontakt" msgstr "Kontakt"
@ -184,6 +170,9 @@ msgstr "Nutzungsbedingungen"
msgid "Finally, an affordable VM hosting in Switzerland!" msgid "Finally, an affordable VM hosting in Switzerland!"
msgstr "Endlich: bezahlbares VM Hosting in der Schweiz" msgstr "Endlich: bezahlbares VM Hosting in der Schweiz"
msgid "Highlights"
msgstr ""
msgid "I want it!" msgid "I want it!"
msgstr "Das will ich haben!" msgstr "Das will ich haben!"
@ -214,6 +203,9 @@ msgstr ""
"mit FOSS (Free Open Source Software) arbeitet und wir daher auf " "mit FOSS (Free Open Source Software) arbeitet und wir daher auf "
"Lizenzgebühren verzichten können." "Lizenzgebühren verzichten können."
msgid "Scale out"
msgstr "Skalierung"
msgid "" msgid ""
"We don't use special hardware. We use commodity hardware: we buy computers " "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 " "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 " "erschwingliche Systeme. Bei grösserer Auslastung werden mehr "
"Standardkomponenten hinzugekauft und skalieren so das Datencenter." "Standardkomponenten hinzugekauft und skalieren so das Datencenter."
msgid "Reliable and light"
msgstr "Zuverlässig und leicht"
msgid "" msgid ""
"Our VMs are located in Switzerland, with reliable power supply and fast " "Our VMs are located in Switzerland, with reliable power supply and fast "
"internet connection. Our VM costs less thanks to our featherlight " "internet connection. Our VM costs less thanks to our featherlight "
@ -232,8 +227,7 @@ msgstr ""
"Energieversorgung, sowie schneller Internetverbindung ausgestattet. Unser " "Energieversorgung, sowie schneller Internetverbindung ausgestattet. Unser "
"Angebot ist aufgrund unserer leichten Infrastruktur überaus kostengünstig." "Angebot ist aufgrund unserer leichten Infrastruktur überaus kostengünstig."
msgid "" msgid "Simple and affordable: Try our virtual machine with featherlight price."
"Simple and affordable: Try our virtual machine with featherlight price."
msgstr "" msgstr ""
"Einfach und bezahlbar: Teste nun unsere virtuellen Maschinen mit " "Einfach und bezahlbar: Teste nun unsere virtuellen Maschinen mit "
"federleichten Preisen." "federleichten Preisen."
@ -314,6 +308,9 @@ msgstr "Gesamt"
msgid "including VAT" msgid "including VAT"
msgstr "inkl. Mehrwertsteuer" msgstr "inkl. Mehrwertsteuer"
msgid "excluding VAT"
msgstr "exkl. Mehrwertsteuer"
msgid "Month" msgid "Month"
msgstr "Monat" msgstr "Monat"
@ -321,13 +318,13 @@ msgid "Credit Card"
msgstr "Kreditkarte" msgstr "Kreditkarte"
msgid "" msgid ""
"Please fill in your credit card information below. We are using <a " "Please fill in your credit card information below. We are using <a href="
"href=\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do " "\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do not "
"not store your information in our database." "store your information in our database."
msgstr "" msgstr ""
"Bitte fülle Deine Kreditkarteninformationen unten aus. Wir nutzen <a " "Bitte fülle Deine Kreditkarteninformationen unten aus. Wir nutzen <a href="
"href=\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung " "\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und "
"und speichern keine Informationen in unserer Datenbank." "speichern keine Informationen in unserer Datenbank."
msgid "" msgid ""
"You are not making any payment yet. After submitting your card information, " "You are not making any payment yet. After submitting your card information, "
@ -383,13 +380,18 @@ msgstr "Bestellungsübersicht"
msgid "Product" msgid "Product"
msgstr "Produkt" msgstr "Produkt"
#, python-format msgid "Subtotal"
msgstr "Zwischensumme"
msgid "VAT"
msgstr "Mehrwertsteuer"
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 "
"with the fee of %(vm_price)sCHF/month" "with the fee of %(vm_total_price)s CHF/month"
msgstr "" msgstr ""
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(vm_price)sCHF " "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
"pro Monat belastet" "%(vm_total_price)s CHF pro Monat belastet"
msgid "Place order" msgid "Place order"
msgstr "Bestellen" msgstr "Bestellen"
@ -459,21 +461,21 @@ msgid ""
"community." "community."
msgstr "" msgstr ""
"Data Center Light ist ein Teil der Free und Opens Source Software (FOSS) " "Data Center Light ist ein Teil der Free und Opens Source Software (FOSS) "
"Bewegung.<br/> Wir sind damit gross geworden, leben damit und glauben " "Bewegung.<br/> Wir sind damit gross geworden, leben damit und glauben daran."
"daran.<br/> Je weiter wir mit unserem Data Center Light vorankommen, desto " "<br/> Je weiter wir mit unserem Data Center Light vorankommen, desto mehr "
"mehr können wir etwas an die FOSS Community zurückgeben." "können wir etwas an die FOSS Community zurückgeben."
msgid "We bring the future to you." msgid "We bring the future to you."
msgstr "Wir bringen die Zukunft zu dir." msgstr "Wir bringen die Zukunft zu dir."
msgid "" msgid ""
"Data Center Light uses the most modern technologies out there.<br>Your VM " "Data Center Light uses the most modern technologies out there.<br>Your VM "
"needs only IPv6. Data Center Light provides<br> transparent two-way " "needs only IPv6. Data Center Light provides<br> transparent two-way IPv6/"
"IPv6/IPv4 translation." "IPv4 translation."
msgstr "" msgstr ""
"Data Center Light verwendet die zur Zeit modernsten Technologien.<br/>Deine " "Data Center Light verwendet die zur Zeit modernsten Technologien.<br/>Deine "
"VM läuft mit IPv6. Data Center Light bietet eine transparente " "VM läuft mit IPv6. Data Center Light bietet eine transparente IPv6/IPv4-"
"IPv6/IPv4-Zweiweglösung." "Zweiweglösung."
msgid "" msgid ""
" No more spinning metal plates! Data Center Light uses only SSDs. We keep " " 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" msgid "Invalid storage size"
msgstr "Ungültige Speicher-Grösse" msgstr "Ungültige Speicher-Grösse"
#, python-brace-format
msgid "Incorrect pricing name. Please contact support{support_email}"
msgstr ""
msgid "Confirm Order" msgid "Confirm Order"
msgstr "Bestellung Bestätigen" msgstr "Bestellung Bestätigen"
@ -520,6 +526,12 @@ msgstr ""
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " "Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
"auf sie zugreifen kannst." "auf sie zugreifen kannst."
#~ msgid "Pricing"
#~ msgstr "Preise"
#~ msgid "Order VM"
#~ msgstr "VM bestellen"
#~ msgid "Enter name" #~ msgid "Enter name"
#~ msgstr "Name" #~ msgstr "Name"
@ -533,18 +545,19 @@ msgstr ""
#~ msgstr "Anfrage verschickt" #~ msgstr "Anfrage verschickt"
#~ msgid "" #~ msgid ""
#~ "Thank you for your subscription! You will receive a confirmation mail from " #~ "Thank you for your subscription! You will receive a confirmation mail "
#~ "our team" #~ "from our team"
#~ msgstr "" #~ msgstr ""
#~ "Vielen dank für Ihre Anmeldung. Sie erhalten in kürze eine Bestätigungsmail " #~ "Vielen dank für Ihre Anmeldung. Sie erhalten in kürze eine "
#~ "von unserem Team" #~ "Bestätigungsmail von unserem Team"
#~ msgid "Thank you for your request." #~ msgid "Thank you for your request."
#~ msgstr "Vielen Dank für Deine Anfrage." #~ msgstr "Vielen Dank für Deine Anfrage."
#~ msgid "You are one step away from being our beta tester!" #~ msgid "You are one step away from being our beta tester!"
#~ msgstr "" #~ 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 "" #~ msgid ""
#~ "Currently we are running our tests to make sure everything runs perfectly." #~ "Currently we are running our tests to make sure everything runs perfectly."
@ -553,8 +566,8 @@ msgstr ""
#~ "sicherzustellen." #~ "sicherzustellen."
#~ msgid "" #~ msgid ""
#~ "In the meantime, we would like to ask you a little patience<br/> until our " #~ "In the meantime, we would like to ask you a little patience<br/> until "
#~ "team contacts you with beta access." #~ "our team contacts you with beta access."
#~ msgstr "" #~ msgstr ""
#~ "Wir werden dann sobald als möglich Ihren Beta-Zugang erstellen und Sie " #~ "Wir werden dann sobald als möglich Ihren Beta-Zugang erstellen und Sie "
#~ "daraufhin kontaktieren.Bis dahin bitten wir Sie um etwas Geduld." #~ "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" #~ msgid "Thank you for order! Our team will contact you via email"
#~ msgstr "" #~ msgstr ""
#~ "Vielen Dank für die Bestellung. Unser Team setzt sich sobald wie möglich mit" #~ "Vielen Dank für die Bestellung. Unser Team setzt sich sobald wie möglich "
#~ " Dir via E-Mail in Verbindung." #~ "mit Dir via E-Mail in Verbindung."
#~ msgid "Affordable VM hosting based in Switzerland" #~ msgid "Affordable VM hosting based in Switzerland"
#~ msgstr "Bezahlbares VM Hosting in der Schweiz" #~ msgstr "Bezahlbares VM Hosting in der Schweiz"
@ -581,18 +594,18 @@ msgstr ""
#~ msgid "" #~ msgid ""
#~ "Our VMs are hosted in Glarus, Switzerland, and our website is currently " #~ "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 " #~ "running in BETA mode. If you want more information that you did not find "
#~ "our website, or if your order is more detailed, or if you encounter any " #~ "on our website, or if your order is more detailed, or if you encounter "
#~ "technical hiccups, please contact us at support@datacenterlight.ch, our team" #~ "any technical hiccups, please contact us at support@datacenterlight.ch, "
#~ " will get in touch with you asap." #~ "our team will get in touch with you asap."
#~ msgstr "" #~ msgstr ""
#~ "Unsere VMs werden in der Schweiz im Kanton Glarus gehostet und befinden sich" #~ "Unsere VMs werden in der Schweiz im Kanton Glarus gehostet und befinden "
#~ " zur Zeit noch in der BETA-Phase. Möchtest du mehr über uns erfahren und " #~ "sich zur Zeit noch in der BETA-Phase. Möchtest du mehr über uns erfahren "
#~ "hast auf unserer Website nicht genügend Informationen gefunden? Möchtest " #~ "und hast auf unserer Website nicht genügend Informationen gefunden? "
#~ "eine detailliertere Bestellung aufgeben? Bist du auf technische Probleme " #~ "Möchtest eine detailliertere Bestellung aufgeben? Bist du auf technische "
#~ "gestossen, die du uns mitteilen möchtest? Dann zögere nicht und kontaktiere " #~ "Probleme gestossen, die du uns mitteilen möchtest? Dann zögere nicht und "
#~ "uns unter support@datacenterlight.ch. Unser Team wird sich umgehend um dein " #~ "kontaktiere uns unter support@datacenterlight.ch. Unser Team wird sich "
#~ "Anliegen kümmern!" #~ "umgehend um dein Anliegen kümmern!"
#~ msgid "is not a proper name" #~ msgid "is not a proper name"
#~ msgstr "ist kein gültiger Name" #~ msgstr "ist kein gültiger Name"
@ -610,12 +623,14 @@ msgstr ""
#~ "\n" #~ "\n"
#~ "Hi,\n" #~ "Hi,\n"
#~ "\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 "" #~ msgstr ""
#~ "\n" #~ "\n"
#~ "Hallo,\n" #~ "Hallo,\n"
#~ "\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" #~ msgid "Your"
#~ msgstr "Dein" #~ msgstr "Dein"
@ -650,12 +665,14 @@ msgstr ""
#~ msgid "I want to have it!" #~ msgid "I want to have it!"
#~ msgstr "Das möchte ich haben!" #~ 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 "" #~ msgstr ""
#~ "Nachhaltigkeit: Wiederverwendung ehemaliger Fabrikhallen an Stelle der " #~ "Nachhaltigkeit: Wiederverwendung ehemaliger Fabrikhallen an Stelle der "
#~ "Errichtung eines neuen Gebäudes" #~ "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 "" #~ msgstr ""
#~ "Kreativität: Verwendung eines modernen und alternativen Designs für unser " #~ "Kreativität: Verwendung eines modernen und alternativen Designs für unser "
#~ "Datencenter" #~ "Datencenter"
@ -678,8 +695,8 @@ msgstr ""
#~ msgstr "Standort des Datacenters ist in der Schweiz" #~ msgstr "Standort des Datacenters ist in der Schweiz"
#~ msgid "" #~ msgid ""
#~ " WARNING: We are currently running in BETA mode. We hope you won't encounter" #~ " WARNING: We are currently running in BETA mode. We hope you won't "
#~ " any hiccups, but if you do, please let us know at " #~ "encounter any hiccups, but if you do, please let us know at "
#~ "support@datacenterlight.ch" #~ "support@datacenterlight.ch"
#~ msgstr "" #~ msgstr ""
#~ " Achtung: Wir befinden uns zurzeit im Beta-Release. Wir hoffen, dass Sie " #~ " Achtung: Wir befinden uns zurzeit im Beta-Release. Wir hoffen, dass Sie "
@ -693,8 +710,8 @@ msgstr ""
#~ msgstr "Unser Versprechen" #~ msgstr "Unser Versprechen"
#~ msgid "" #~ msgid ""
#~ "Instead of creating an expensive SLA for availability, we promise that we do" #~ "Instead of creating an expensive SLA for availability, we promise that we "
#~ " our best to run things as smooth as possible." #~ "do our best to run things as smooth as possible."
#~ msgstr "" #~ msgstr ""
#~ "Anstatt eines SLAs (Service Levle Agreements) zu vereinbaren,setzen wir " #~ "Anstatt eines SLAs (Service Levle Agreements) zu vereinbaren,setzen wir "
#~ "unsere persönliche Arbeitskraft ein, um Ihnen ein sorgenfreiesHosting zu " #~ "unsere persönliche Arbeitskraft ein, um Ihnen ein sorgenfreiesHosting zu "

View file

@ -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
)
)

View 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'),
),
]

View file

@ -1,5 +1,9 @@
import logging
from django.db import models from django.db import models
logger = logging.getLogger(__name__)
class VMTemplate(models.Model): class VMTemplate(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
@ -12,6 +16,59 @@ class VMTemplate(models.Model):
return vm_template 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): class StripePlan(models.Model):
""" """
A model to store Data Center Light's created Stripe plans A model to store Data Center Light's created Stripe plans

View file

@ -120,6 +120,11 @@
.header_slider .intro-cap { .header_slider .intro-cap {
font-size: 3.25em; font-size: 3.25em;
} }
.header_slider > .carousel .item .container {
padding-left: 0;
padding-right: 0;
}
} }
.header_slider .intro_lead { .header_slider .intro_lead {

View file

@ -1231,6 +1231,15 @@ footer {
background-position: center; background-position: center;
} }
.promo-section.promo-with-bg a {
color: #87B6EA;
}
.promo-section.promo-with-bg a:hover,
.promo-section.promo-with-bg a:focus {
color: #77a6da;
}
.promo-section h3 { .promo-section h3 {
font-weight: 700; font-weight: 700;
font-size: 36px; font-size: 36px;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -171,7 +171,18 @@
} }
function _calcPricing() { 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 = parseFloat(total.toFixed(2));
$("#total").text(total); $("#total").text(total);
} }

View file

@ -19,6 +19,8 @@ from utils.forms import UserBillingAddressForm
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.models import BillingAddress from utils.models import BillingAddress
from .models import VMPricing
logger = get_task_logger(__name__) 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)) "Running create_vm_task on {}".format(current_task.request.hostname))
vm_id = None vm_id = None
try: try:
final_price = specs.get('price') final_price = (specs.get('total_price') if 'total_price' in specs
else specs.get('price'))
billing_address = BillingAddress( billing_address = BillingAddress(
cardholder_name=billing_address_data['cardholder_name'], cardholder_name=billing_address_data['cardholder_name'],
street_address=billing_address_data['street_address'], 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: if vm_id is None:
raise Exception("Could not create VM") 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 # Create a Hosting Order
order = HostingOrder.create( order = HostingOrder.create(
price=final_price, price=final_price,
vm_id=vm_id, vm_id=vm_id,
customer=customer, customer=customer,
billing_address=billing_address billing_address=billing_address,
vm_pricing=vm_pricing
) )
# Create a Hosting Bill # Create a Hosting Bill
HostingBill.create( 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 # Create Billing Address for User if he does not have one
if not customer.user.billing_addresses.count(): 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'), 'cores': specs.get('cpu'),
'memory': specs.get('memory'), 'memory': specs.get('memory'),
'storage': specs.get('disk_size'), 'storage': specs.get('disk_size'),
'price': specs.get('price'), 'price': final_price,
'template': template.get('name'), 'template': template.get('name'),
'vm_name': vm.get('name'), 'vm_name': vm.get('name'),
'vm_id': vm['vm_id'], 'vm_id': vm['vm_id'],
'order_id': order.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 = { email_data = {
'subject': settings.DCL_TEXT + " Order from %s" % context['email'], 'subject': settings.DCL_TEXT + " Order from %s" % context['email'],
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,

View file

@ -14,7 +14,7 @@
<table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;"> <table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;">
<tr> <tr>
<td> <td>
<img src="{{ base_url }}{% static 'datacenterlight/img/logo_black.png' %}" style="width: 200px; height: 50px;"> <img src="{{ base_url }}{% static 'datacenterlight/img/datacenterlight.png' %}" style="max-width: 200px;">
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -14,7 +14,7 @@
<table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;"> <table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;">
<tr> <tr>
<td> <td>
<img src="{{ base_url }}{% static 'datacenterlight/img/logo_black.png' %}" style="width: 200px; height: 50px;"> <img src="{{ base_url }}{% static 'datacenterlight/img/datacenterlight.png' %}" style="max-width: 200px;">
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -1,4 +1,16 @@
{% load staticfiles i18n%} {% 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"> <form id="order_form" method="POST" action="{% url 'datacenterlight:index' %}" data-toggle="validator" role="form">
{% csrf_token %} {% csrf_token %}
<div class="title"> <div class="title">
@ -7,9 +19,11 @@
<div class="price"> <div class="price">
<span id="total">15</span> <span id="total">15</span>
<span>CHF/{% trans "month" %}</span> <span>CHF/{% trans "month" %}</span>
{% if vm_pricing.vat_inclusive %}
<div class="price-text"> <div class="price-text">
<p>{% trans "VAT included" %}</p> <p>{% trans "VAT included" %}</p>
</div> </div>
{% endif %}
</div> </div>
<div class="descriptions"> <div class="descriptions">
<div class="description form-group"> <div class="description form-group">
@ -78,5 +92,6 @@
</select> </select>
</div> </div>
</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> <input type="submit" class="btn btn-primary disabled" value="{% trans 'Continue' %}"></input>
</form> </form>

View file

@ -78,7 +78,7 @@
<hr> <hr>
<p>{% trans "Configuration"%} <strong class="pull-right">{{request.session.template.name}}</strong></p> <p>{% trans "Configuration"%} <strong class="pull-right">{{request.session.template.name}}</strong></p>
<hr> <hr>
<p class="last-p"><strong>{%trans "Total" %}</strong>&nbsp;&nbsp;<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>&nbsp;&nbsp;<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> </div>
</div> </div>

View file

@ -65,9 +65,19 @@
<span>{% trans "Disk space" %}: </span> <span>{% trans "Disk space" %}: </span>
<span class="pull-right">{{vm.disk_size|intcomma}} GB</span> <span class="pull-right">{{vm.disk_size|intcomma}} GB</span>
</p> </p>
{% if vm.vat > 0 %}
<p> <p>
<span>{% trans "Total" %}</span> <strong>{% trans "Subtotal" %}: </strong>
<span class="pull-right">{{vm.price|intcomma}} CHF</span> <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> </p>
</div> </div>
</div> </div>
@ -78,7 +88,7 @@
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="row">
<div class="col-sm-8"> <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>
<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">

View file

@ -19,11 +19,11 @@ from hosting.models import HostingOrder
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 BillingAddressForm, BillingAddressFormSignup 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.stripe_utils import StripeUtils
from utils.tasks import send_plain_email_task from utils.tasks import send_plain_email_task
from .forms import ContactForm from .forms import ContactForm
from .models import VMTemplate from .models import VMTemplate, VMPricing
from .utils import get_cms_integration from .utils import get_cms_integration
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -93,7 +93,8 @@ 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):
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: if session_var in request.session:
del request.session[session_var] del request.session[session_var]
return HttpResponseRedirect(reverse('datacenterlight:cms_index')) return HttpResponseRedirect(reverse('datacenterlight:cms_index'))
@ -106,12 +107,30 @@ class IndexView(CreateView):
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'))
pricing_name = request.POST.get('pricing_name')
vm_pricing = VMPricing.get_vm_pricing_by_name(pricing_name)
template = VMTemplate.objects.filter( template = VMTemplate.objects.filter(
opennebula_vm_template_id=template_id opennebula_vm_template_id=template_id
).first() ).first()
template_data = VMTemplateSerializer(template).data template_data = VMTemplateSerializer(template).data
referer_url = request.META['HTTP_REFERER'] 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: try:
cores = cores_field.clean(cores) cores = cores_field.clean(cores)
except ValidationError as err: except ValidationError as err:
@ -139,14 +158,21 @@ class IndexView(CreateView):
) )
return HttpResponseRedirect(referer_url + "#order_form") return HttpResponseRedirect(referer_url + "#order_form")
amount_to_be_charged = get_vm_price( price, vat, vat_percent = get_vm_price_with_vat(
cpu=cores, memory=memory, disk_size=storage cpu=cores,
memory=memory,
ssd_size=storage,
pricing_name=vm_pricing_name
) )
specs = { specs = {
'cpu': cores, 'cpu': cores,
'memory': memory, 'memory': memory,
'disk_size': storage, '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['specs'] = specs
request.session['template'] = template_data request.session['template'] = template_data
@ -220,7 +246,10 @@ class PaymentOrderView(FormView):
'site_url': reverse('datacenterlight:index'), 'site_url': reverse('datacenterlight:index'),
'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'),
'vm_pricing': VMPricing.get_vm_pricing_by_name(
self.request.session['specs']['pricing_name']
)
}) })
return context return context
@ -393,7 +422,7 @@ class OrderConfirmationView(DetailView):
cpu = specs.get('cpu') cpu = specs.get('cpu')
memory = specs.get('memory') memory = specs.get('memory')
disk_size = specs.get('disk_size') disk_size = specs.get('disk_size')
amount_to_be_charged = specs.get('price') amount_to_be_charged = specs.get('total_price')
plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu, plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
memory=memory, memory=memory,
disk_size=disk_size) disk_size=disk_size)
@ -489,7 +518,7 @@ class OrderConfirmationView(DetailView):
stripe_subscription_obj.id, card_details_dict) stripe_subscription_obj.id, card_details_dict)
for session_var in ['specs', 'template', 'billing_address', for session_var in ['specs', 'template', 'billing_address',
'billing_address_data', 'billing_address_data',
'token', 'customer']: 'token', 'customer', 'pricing_name']:
if session_var in request.session: if session_var in request.session:
del request.session[session_var] del request.session[session_var]

View file

@ -67,6 +67,7 @@ urlpatterns += i18n_patterns(
include('ungleich_page.urls', include('ungleich_page.urls',
namespace='ungleich_page'), namespace='ungleich_page'),
name='ungleich_page'), name='ungleich_page'),
url(r'^blog/', include('djangocms_blog.urls', namespace='djangocms_blog')),
) )
urlpatterns += [ urlpatterns += [

View 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,
),
]

View file

@ -6,6 +6,8 @@ from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from datacenterlight.models import VMPricing
from membership.models import StripeCustomer, CustomUser from membership.models import StripeCustomer, CustomUser
from utils.models import BillingAddress from utils.models import BillingAddress
from utils.mixins import AssignPermissionsMixin from utils.mixins import AssignPermissionsMixin
@ -53,6 +55,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
stripe_charge_id = models.CharField(max_length=100, null=True) stripe_charge_id = models.CharField(max_length=100, null=True)
price = models.FloatField() price = models.FloatField()
subscription_id = models.CharField(max_length=100, null=True) subscription_id = models.CharField(max_length=100, null=True)
vm_pricing = models.ForeignKey(VMPricing)
permissions = ('view_hostingorder',) permissions = ('view_hostingorder',)
@ -70,12 +73,13 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
@classmethod @classmethod
def create(cls, price=None, vm_id=None, customer=None, def create(cls, price=None, vm_id=None, customer=None,
billing_address=None): billing_address=None, vm_pricing=None):
instance = cls.objects.create( instance = cls.objects.create(
price=price, price=price,
vm_id=vm_id, vm_id=vm_id,
customer=customer, customer=customer,
billing_address=billing_address billing_address=billing_address,
vm_pricing=vm_pricing
) )
instance.assign_permissions(customer.user) instance.assign_permissions(customer.user)
return instance return instance

View file

@ -14,7 +14,7 @@
<table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;"> <table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;">
<tr> <tr>
<td> <td>
<img src="{{ base_url }}{% static 'datacenterlight/img/logo_black.png' %}" style="width: 200px; height: 50px;"> <img src="{{ base_url }}{% static 'datacenterlight/img/datacenterlight.png' %}" style="max-width: 200px;">
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -14,7 +14,7 @@
<table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;"> <table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;">
<tr> <tr>
<td> <td>
<img src="{{base_url}}{% static 'datacenterlight/img/logo_black.png' %}" style="width: 200px; height: 50px;"> <img src="{{ base_url }}{% static 'datacenterlight/img/datacenterlight.png' %}" style="max-width: 200px;">
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -14,7 +14,7 @@
<table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;"> <table style="width: 100%; border-spacing: 0; border-collapse: collapse; max-width: 560px;">
<tr> <tr>
<td> <td>
<img src="{{ base_url }}{% static 'datacenterlight/img/logo_black.png' %}" style="width: 200px; height: 50px;"> <img src="{{ base_url }}{% static 'datacenterlight/img/datacenterlight.png' %}" style="max-width: 200px;">
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -74,7 +74,7 @@
<center style="font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;"> <center style="font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;">
<table cellpadding="0" cellspacing="0" width="600" class="w320" style="border-collapse: collapse !important; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;"><tr style="font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;"> <table cellpadding="0" cellspacing="0" width="600" class="w320" style="border-collapse: collapse !important; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;"><tr style="font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important;">
<td class="pull-left mobile-header-padding-left" style="vertical-align: middle; border-collapse: collapse; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; font-size: 14px; color: #777777; text-align: left; line-height: 21px; width: 290px; padding-left: 10px;" align="left" valign="middle"> <td class="pull-left mobile-header-padding-left" style="vertical-align: middle; border-collapse: collapse; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; font-size: 14px; color: #777777; text-align: left; line-height: 21px; width: 290px; padding-left: 10px;" align="left" valign="middle">
<a href="{{base_url}}" style="font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; color: #676767; text-decoration: none !important;"><img width="137" src="{{base_url}}{% static "hosting/img/logo_black.png" %}" alt="logo" style="max-width: 600px; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; border: none;"></a> <a href="{{base_url}}" style="font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; color: #676767; text-decoration: none !important;"><img width="137" src="{{base_url}}{% static 'hosting/img/datacenterlight.png' %}" alt="logo" style="max-width: 600px; outline: none; text-decoration: none; -ms-interpolation-mode: bicubic; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; border: none;"></a>
</td> </td>
<td class="pull-right mobile-header-padding-right" style="color: #4d4d4d; border-collapse: collapse; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; font-size: 14px; text-align: right; line-height: 21px; width: 290px; padding-left: 10px;" align="right"> <td class="pull-right mobile-header-padding-right" style="color: #4d4d4d; border-collapse: collapse; font-family: 'Oxygen', 'Helvetica Neue', 'Arial', 'sans-serif' !important; font-size: 14px; text-align: right; line-height: 21px; width: 290px; padding-left: 10px;" align="right">
</td> </td>

View file

@ -127,9 +127,19 @@
<span>{% trans "Disk space" %}: </span> <span>{% trans "Disk space" %}: </span>
<span class="pull-right">{{vm.disk_size}} GB</span> <span class="pull-right">{{vm.disk_size}} GB</span>
</p> </p>
{% if vm.vat > 0 %}
<p> <p>
<span>{% trans "Total" %}</span> <strong>{% trans "Subtotal" %}: </strong>
<span class="pull-right">{{vm.price|intcomma}} CHF</span> <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> </p>
</div> </div>
</div> </div>

View file

@ -29,7 +29,7 @@
<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" data-header="{% trans 'Date' %}">{{ order.created_at | date:"M d, Y H:i" }}</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"> <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>
</td> </td>

View file

@ -45,7 +45,7 @@
<h2 class="vm-detail-title">{% trans "Billing" %} <img src="{% static 'hosting/img/billing.svg' %}" class="un-icon"></h2> <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-vmid">
<div class="vm-item-subtitle">{% trans "Current Pricing" %}</div> <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> <a class="btn btn-vm-invoice" href="{% url 'hosting:orders' order.pk %}">{% trans "See Invoice" %}</a>
</div> </div>
</div> </div>

View file

@ -42,7 +42,7 @@ from utils.forms import (
BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm, BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm,
ResendActivationEmailForm 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.mailer import BaseEmail
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.tasks import send_plain_email_task from utils.tasks import send_plain_email_task
@ -749,11 +749,17 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
context['vm'] = vm_detail.__dict__ context['vm'] = vm_detail.__dict__
context['vm']['name'] = '{}-{}'.format( context['vm']['name'] = '{}-{}'.format(
context['vm']['configuration'], context['vm']['vm_id']) 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'], cpu=context['vm']['cores'],
disk_size=context['vm']['disk_size'], ssd_size=context['vm']['disk_size'],
memory=context['vm']['memory'] 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() context['subscription_end_date'] = vm_detail.end_date()
except VMDetail.DoesNotExist: except VMDetail.DoesNotExist:
try: try:
@ -762,6 +768,17 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
) )
vm = manager.get_vm(obj.vm_id) vm = manager.get_vm(obj.vm_id)
context['vm'] = VirtualMachineSerializer(vm).data 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: except WrongIdError:
messages.error( messages.error(
self.request, self.request,
@ -1100,7 +1117,8 @@ class VirtualMachineView(LoginRequiredMixin, View):
context = { context = {
'virtual_machine': serializer.data, 'virtual_machine': serializer.data,
'order': HostingOrder.objects.get( 'order': HostingOrder.objects.get(
vm_id=serializer.data['vm_id']) vm_id=serializer.data['vm_id']
)
} }
except Exception as ex: except Exception as ex:
logger.debug("Exception generated {}".format(str(ex))) logger.debug("Exception generated {}".format(str(ex)))

View file

@ -1,6 +1,8 @@
import decimal
import logging import logging
from oca.pool import WrongIdError from oca.pool import WrongIdError
from datacenterlight.models import VMPricing
from hosting.models import UserHostingKey, VMDetail from hosting.models import UserHostingKey, VMDetail
from opennebula_api.serializers import VirtualMachineSerializer from opennebula_api.serializers import VirtualMachineSerializer
@ -49,14 +51,74 @@ def get_or_create_vm_detail(user, manager, vm_id):
return vm_detail_obj 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 A helper function that computes price of a VM from given cpu, ram and
ssd parameters ssd parameters
:param cpu: Number of cores of the VM :param cpu: Number of cores of the VM
:param memory: RAM 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: 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)