Merge branch 'master' into 5151/gdpr_modal
This commit is contained in:
		
				commit
				
					
						1feacc1770
					
				
			
		
					 39 changed files with 1391 additions and 390 deletions
				
			
		
							
								
								
									
										21
									
								
								Changelog
									
										
									
									
									
								
							
							
						
						
									
										21
									
								
								Changelog
									
										
									
									
									
								
							| 
						 | 
					@ -1,4 +1,25 @@
 | 
				
			||||||
Next:
 | 
					Next:
 | 
				
			||||||
 | 
					    * bugfix: Use correct version of django-multisite (MR #676)
 | 
				
			||||||
 | 
					2.4.1: 2018-10-18
 | 
				
			||||||
 | 
					    * bugfix: Update pycryptodome module from 3.4 to 3.6.6 (PR #674)
 | 
				
			||||||
 | 
					2.4: 2018-10-18
 | 
				
			||||||
 | 
					    * #5681: [hosting,dcl] Allow admin to lower minimum RAM to 512 MB (PR #672)
 | 
				
			||||||
 | 
					2.3.1: 2018-10-17
 | 
				
			||||||
 | 
					    * bugfix: [hosting, dcl] Show VAT percent rounded to 2 decimal places in the order confirmation page (PR #673)
 | 
				
			||||||
 | 
					2.3: 2018-10-08
 | 
				
			||||||
 | 
					    * #5690: Generic payment page - allow admin to add a onetime/monthly product and the frontend for user to pay for this product (PR #666)
 | 
				
			||||||
 | 
					2.2.2: 2018-09-28
 | 
				
			||||||
 | 
					    * #5721: Set calculator OS list in alphabetical order and set `Devuan Ascii` as the default (PR #668)
 | 
				
			||||||
 | 
					    * bugfix: Fix some typos and correct DE translations (PR #667)
 | 
				
			||||||
 | 
					2.2.1: 2018-09-25
 | 
				
			||||||
 | 
					    * feature: Change DCLNavbarPlugin to show login option only if set (PR #665)
 | 
				
			||||||
 | 
					    * bugfix: Log opennebula errors and send proper message when vm terminate is not completed in the stipulated time (PR #648)
 | 
				
			||||||
 | 
					2.2: 2018-09-06
 | 
				
			||||||
 | 
					    * bugfix: Include price in the Stripe plan name to make it distinct and to correct pricing since version 1.9
 | 
				
			||||||
 | 
					2.1.2: 2018-08-30
 | 
				
			||||||
 | 
					    * bugfix: [blog, comic] Set blog rss feed for all blog templates
 | 
				
			||||||
 | 
					2.1.1: 2018-08-24
 | 
				
			||||||
 | 
					    * #5487: [hosting] Add explicit warning message for teminating VM (PR #656)
 | 
				
			||||||
    * bugfix: [dg] Send email to admin on dg subscription and increase cc_brand field to 128 characters (PR #652)
 | 
					    * bugfix: [dg] Send email to admin on dg subscription and increase cc_brand field to 128 characters (PR #652)
 | 
				
			||||||
    * #5458: [admin] Make hostingorder more readable (PR #657)
 | 
					    * #5458: [admin] Make hostingorder more readable (PR #657)
 | 
				
			||||||
    * bugfix: [CMS templates] Set description meta field of ungleich template (was missing before) and set ungleich glarus ag uniformly as author of various CMS pages (PR #653)
 | 
					    * bugfix: [CMS templates] Set description meta field of ungleich template (was missing before) and set ungleich glarus ag uniformly as author of various CMS pages (PR #653)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -180,6 +180,10 @@ class DCLNavbarPluginModel(CMSPlugin):
 | 
				
			||||||
        default=True,
 | 
					        default=True,
 | 
				
			||||||
        help_text='Select to include the language selection dropdown.'
 | 
					        help_text='Select to include the language selection dropdown.'
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    show_login_option = models.BooleanField(
 | 
				
			||||||
 | 
					        default=True,
 | 
				
			||||||
 | 
					        help_text='Uncheck this if you do not want to show login/dashboard.'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_logo_dark(self):
 | 
					    def get_logo_dark(self):
 | 
				
			||||||
        # used only if atleast one logo exists
 | 
					        # used only if atleast one logo exists
 | 
				
			||||||
| 
						 | 
					@ -350,3 +354,11 @@ class DCLCalculatorPluginModel(CMSPlugin):
 | 
				
			||||||
                  "in the backend to be automatically listed in this "
 | 
					                  "in the backend to be automatically listed in this "
 | 
				
			||||||
                  "calculator instance."
 | 
					                  "calculator instance."
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    default_selected_template = models.CharField(
 | 
				
			||||||
 | 
					        default="Devuan Ascii",
 | 
				
			||||||
 | 
					        null=True,
 | 
				
			||||||
 | 
					        max_length=128,
 | 
				
			||||||
 | 
					        help_text="Write the name of the template that you need selected as"
 | 
				
			||||||
 | 
					                  " default when the calculator loads"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    enable_512mb_ram = models.BooleanField(default=False)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ from .cms_models import (
 | 
				
			||||||
    DCLSectionPromoPluginModel, DCLCalculatorPluginModel
 | 
					    DCLSectionPromoPluginModel, DCLCalculatorPluginModel
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .models import VMTemplate
 | 
					from .models import VMTemplate
 | 
				
			||||||
 | 
					from datacenterlight.utils import clear_all_session_vars
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@plugin_pool.register_plugin
 | 
					@plugin_pool.register_plugin
 | 
				
			||||||
| 
						 | 
					@ -85,6 +86,7 @@ class DCLCalculatorPlugin(CMSPluginBase):
 | 
				
			||||||
    require_parent = True
 | 
					    require_parent = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def render(self, context, instance, placeholder):
 | 
					    def render(self, context, instance, placeholder):
 | 
				
			||||||
 | 
					        clear_all_session_vars(context['request'])
 | 
				
			||||||
        context = super(DCLCalculatorPlugin, self).render(
 | 
					        context = super(DCLCalculatorPlugin, self).render(
 | 
				
			||||||
            context, instance, placeholder
 | 
					            context, instance, placeholder
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					@ -92,11 +94,13 @@ class DCLCalculatorPlugin(CMSPluginBase):
 | 
				
			||||||
        if ids:
 | 
					        if ids:
 | 
				
			||||||
            context['templates'] = VMTemplate.objects.filter(
 | 
					            context['templates'] = VMTemplate.objects.filter(
 | 
				
			||||||
                vm_type=instance.vm_type
 | 
					                vm_type=instance.vm_type
 | 
				
			||||||
            ).filter(opennebula_vm_template_id__in=ids)
 | 
					            ).filter(opennebula_vm_template_id__in=ids).order_by('name')
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            context['templates'] = VMTemplate.objects.filter(
 | 
					            context['templates'] = VMTemplate.objects.filter(
 | 
				
			||||||
                vm_type=instance.vm_type
 | 
					                vm_type=instance.vm_type
 | 
				
			||||||
            )
 | 
					            ).order_by('name')
 | 
				
			||||||
 | 
					        context['instance'] = instance
 | 
				
			||||||
 | 
					        context['min_ram'] = 0.5 if instance.enable_512mb_ram else 1
 | 
				
			||||||
        return context
 | 
					        return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ msgid ""
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
					"Project-Id-Version: PACKAGE VERSION\n"
 | 
				
			||||||
"Report-Msgid-Bugs-To: \n"
 | 
					"Report-Msgid-Bugs-To: \n"
 | 
				
			||||||
"POT-Creation-Date: 2018-07-05 23:11+0000\n"
 | 
					"POT-Creation-Date: 2018-09-26 20:44+0000\n"
 | 
				
			||||||
"PO-Revision-Date: 2018-03-30 23:22+0000\n"
 | 
					"PO-Revision-Date: 2018-03-30 23:22+0000\n"
 | 
				
			||||||
"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
 | 
					"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
 | 
				
			||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
					"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
				
			||||||
| 
						 | 
					@ -293,6 +293,9 @@ msgstr "Registrieren"
 | 
				
			||||||
msgid "Billing Address"
 | 
					msgid "Billing Address"
 | 
				
			||||||
msgstr "Rechnungsadresse"
 | 
					msgstr "Rechnungsadresse"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Make a payment"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Your Order"
 | 
					msgid "Your Order"
 | 
				
			||||||
msgstr "Deine Bestellung"
 | 
					msgstr "Deine Bestellung"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -336,9 +339,9 @@ msgid ""
 | 
				
			||||||
"database."
 | 
					"database."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
"Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine "
 | 
					"Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine "
 | 
				
			||||||
"Kreditkartendetails unten an. Die Bezahlung wird über "
 | 
					"Kreditkartendetails unten an. Die Bezahlung wird über <a href=\"https://"
 | 
				
			||||||
"<a href=\"https://stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. "
 | 
					"stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. Wir speichern Deine "
 | 
				
			||||||
"Wir speichern Deine Kreditkartendetails nicht in unserer Datenbank."
 | 
					"Kreditkartendetails nicht in unserer Datenbank."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid ""
 | 
					msgid ""
 | 
				
			||||||
"Please fill in your credit card information below. We are using <a href="
 | 
					"Please fill in your credit card information below. We are using <a href="
 | 
				
			||||||
| 
						 | 
					@ -395,12 +398,35 @@ msgstr "Bestellungsübersicht"
 | 
				
			||||||
msgid "Product"
 | 
					msgid "Product"
 | 
				
			||||||
msgstr "Produkt"
 | 
					msgstr "Produkt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Amount"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Description"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Recurring"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Subtotal"
 | 
					msgid "Subtotal"
 | 
				
			||||||
msgstr "Zwischensumme"
 | 
					msgstr "Zwischensumme"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "VAT"
 | 
					msgid "VAT"
 | 
				
			||||||
msgstr "Mehrwertsteuer"
 | 
					msgstr "Mehrwertsteuer"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					"By clicking \"Place order\" this plan will charge your credit card account "
 | 
				
			||||||
 | 
					"with %(total_price)s CHF/month"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
 | 
				
			||||||
 | 
					"%(vm_total_price)s CHF pro Monat belastet"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					"By clicking \"Place order\" this payment will charge your credit card "
 | 
				
			||||||
 | 
					"account with a one time amount of %(total_price)s CHF"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
 | 
				
			||||||
 | 
					"%(vm_total_price)s CHF pro Monat belastet"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid ""
 | 
					msgid ""
 | 
				
			||||||
"By clicking \"Place order\" this plan will charge your credit card account "
 | 
					"By clicking \"Place order\" this plan will charge your credit card account "
 | 
				
			||||||
| 
						 | 
					@ -541,8 +567,33 @@ msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#, python-brace-format
 | 
					#, python-brace-format
 | 
				
			||||||
msgid "An error occurred while associating the card. Details: {details}"
 | 
					msgid "An error occurred while associating the card. Details: {details}"
 | 
				
			||||||
msgstr "Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: "
 | 
					msgstr ""
 | 
				
			||||||
"{details}"
 | 
					"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Confirmation of your payment"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid " This is a monthly recurring plan."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#, python-brace-format
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					"Hi {name},\n"
 | 
				
			||||||
 | 
					"\n"
 | 
				
			||||||
 | 
					"thank you for your order!\n"
 | 
				
			||||||
 | 
					"We have just received a payment of CHF {amount:.2f} from you.{recurring}\n"
 | 
				
			||||||
 | 
					"\n"
 | 
				
			||||||
 | 
					"Cheers,\n"
 | 
				
			||||||
 | 
					"Your Data Center Light team"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Thank you for the payment."
 | 
				
			||||||
 | 
					msgstr "Danke für Deine Bestellung."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					"You will soon receive a confirmation email of the payment. You can always "
 | 
				
			||||||
 | 
					"contact us at info@ungleich.ch for any question that you may have."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Thank you for the order."
 | 
					msgid "Thank you for the order."
 | 
				
			||||||
msgstr "Danke für Deine Bestellung."
 | 
					msgstr "Danke für Deine Bestellung."
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					# Generated by Django 1.9.4 on 2018-09-25 20:27
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('datacenterlight', '0024_dclcalculatorpluginmodel_vm_templates_to_show'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='dclnavbarpluginmodel',
 | 
				
			||||||
 | 
					            name='show_login_option',
 | 
				
			||||||
 | 
					            field=models.BooleanField(default=True, help_text='Uncheck this if you do not want to show login/dashboard.'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					# Generated by Django 1.9.4 on 2018-09-27 20:32
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('datacenterlight', '0025_dclnavbarpluginmodel_show_login_option'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='dclcalculatorpluginmodel',
 | 
				
			||||||
 | 
					            name='default_selected_template',
 | 
				
			||||||
 | 
					            field=models.CharField(default='Devuan Ascii', help_text='Write the name of the template that you need selected as default when the calculator loads', max_length=128, null=True),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					# Generated by Django 1.9.4 on 2018-09-29 05:36
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('datacenterlight', '0026_dclcalculatorpluginmodel_default_selected_template'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='dclcalculatorpluginmodel',
 | 
				
			||||||
 | 
					            name='enable_512mb_ram',
 | 
				
			||||||
 | 
					            field=models.BooleanField(default=False),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					@ -179,4 +179,10 @@ footer .dcl-link-separator::before {
 | 
				
			||||||
.new-card-button-margin button{
 | 
					.new-card-button-margin button{
 | 
				
			||||||
    margin-top: 5px;
 | 
					    margin-top: 5px;
 | 
				
			||||||
    margin-bottom: 5px;
 | 
					    margin-bottom: 5px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.input-no-border {
 | 
				
			||||||
 | 
					  border: none !important;
 | 
				
			||||||
 | 
					  background: transparent !important;
 | 
				
			||||||
 | 
					  resize: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,10 @@
 | 
				
			||||||
    /* ---------------------------------------------
 | 
					    /* ---------------------------------------------
 | 
				
			||||||
     Scripts initialization
 | 
					     Scripts initialization
 | 
				
			||||||
     --------------------------------------------- */
 | 
					     --------------------------------------------- */
 | 
				
			||||||
 | 
					    var minRam = 1;
 | 
				
			||||||
 | 
					    if(window.minRam){
 | 
				
			||||||
 | 
					        minRam = window.minRam;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    var cardPricing = {
 | 
					    var cardPricing = {
 | 
				
			||||||
        'cpu': {
 | 
					        'cpu': {
 | 
				
			||||||
            'id': 'coreValue',
 | 
					            'id': 'coreValue',
 | 
				
			||||||
| 
						 | 
					@ -16,7 +20,7 @@
 | 
				
			||||||
        'ram': {
 | 
					        'ram': {
 | 
				
			||||||
            'id': 'ramValue',
 | 
					            'id': 'ramValue',
 | 
				
			||||||
            'value': 2,
 | 
					            'value': 2,
 | 
				
			||||||
            'min': 1,
 | 
					            'min': minRam,
 | 
				
			||||||
            'max': 200,
 | 
					            'max': 200,
 | 
				
			||||||
            'interval': 1
 | 
					            'interval': 1
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					@ -40,6 +44,7 @@
 | 
				
			||||||
        _initNavUrl();
 | 
					        _initNavUrl();
 | 
				
			||||||
        _initPricing();
 | 
					        _initPricing();
 | 
				
			||||||
        ajaxForms();
 | 
					        ajaxForms();
 | 
				
			||||||
 | 
					        $('#ramValue').data('old-value', $('#ramValue').val());
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $(window).resize(function() {
 | 
					    $(window).resize(function() {
 | 
				
			||||||
| 
						 | 
					@ -144,21 +149,54 @@
 | 
				
			||||||
            var data = $(this).data('minus');
 | 
					            var data = $(this).data('minus');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (cardPricing[data].value > cardPricing[data].min) {
 | 
					            if (cardPricing[data].value > cardPricing[data].min) {
 | 
				
			||||||
                cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
 | 
					                if(data === 'ram' && String(cardPricing[data].value) === "1" && minRam === 0.5){
 | 
				
			||||||
 | 
					                    cardPricing[data].value = 0.5;
 | 
				
			||||||
 | 
					                    $('#ramValue').val('0.5');
 | 
				
			||||||
 | 
					                    $("#ramValue").attr('step', 0.5);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            _fetchPricing();
 | 
					            _fetchPricing();
 | 
				
			||||||
 | 
					            $('#ramValue').data('old-value', $('#ramValue').val());
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        $('.fa-plus-circle.right').click(function(event) {
 | 
					        $('.fa-plus-circle.right').click(function(event) {
 | 
				
			||||||
            var data = $(this).data('plus');
 | 
					            var data = $(this).data('plus');
 | 
				
			||||||
            if (cardPricing[data].value < cardPricing[data].max) {
 | 
					            if (cardPricing[data].value < cardPricing[data].max) {
 | 
				
			||||||
                cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
 | 
					                if(data === 'ram' && String(cardPricing[data].value) === "0.5" && minRam === 0.5){
 | 
				
			||||||
 | 
					                    cardPricing[data].value = 1;
 | 
				
			||||||
 | 
					                    $('#ramValue').val('1');
 | 
				
			||||||
 | 
					                    $("#ramValue").attr('step', 1);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            _fetchPricing();
 | 
					            _fetchPricing();
 | 
				
			||||||
 | 
					            $('#ramValue').data('old-value', $('#ramValue').val());
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $('.input-price').change(function() {
 | 
					        $('.input-price').change(function() {
 | 
				
			||||||
            var data = $(this).attr("name");
 | 
					            var data = $(this).attr("name");
 | 
				
			||||||
            cardPricing[data].value = $('input[name=' + data + ']').val();
 | 
					            var input = $('input[name=' + data + ']');
 | 
				
			||||||
 | 
					            var inputValue = input.val();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if(data === 'ram') {
 | 
				
			||||||
 | 
					                var ramInput = $('#ramValue');
 | 
				
			||||||
 | 
					                if ($('#ramValue').data('old-value') < $('#ramValue').val()) {
 | 
				
			||||||
 | 
					                    if($('#ramValue').val() === '1' && minRam === 0.5) {
 | 
				
			||||||
 | 
					                        $("#ramValue").attr('step', 1);
 | 
				
			||||||
 | 
					                        $('#ramValue').val('1');
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    if($('#ramValue').val() === '0' && minRam === 0.5) {
 | 
				
			||||||
 | 
					                        $("#ramValue").attr('step', 0.5);
 | 
				
			||||||
 | 
					                        $('#ramValue').val('0.5');
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                inputValue = $('#ramValue').val();
 | 
				
			||||||
 | 
					                $('#ramValue').data('old-value', $('#ramValue').val());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            cardPricing[data].value = inputValue;
 | 
				
			||||||
            _fetchPricing();
 | 
					            _fetchPricing();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,14 +35,16 @@
 | 
				
			||||||
          {% endif %}
 | 
					          {% endif %}
 | 
				
			||||||
        </li>
 | 
					        </li>
 | 
				
			||||||
      {% endif %}
 | 
					      {% endif %}
 | 
				
			||||||
      {% if not request.user.is_authenticated %}
 | 
					      {% if instance.show_login_option %}
 | 
				
			||||||
        <li>
 | 
					        {% if not request.user.is_authenticated %}
 | 
				
			||||||
          <a href="{% url 'hosting:login' %}">{% trans "Login" %}  <span class="fa fa-sign-in"></span></a>
 | 
					          <li>
 | 
				
			||||||
        </li>
 | 
					            <a href="{% url 'hosting:login' %}">{% trans "Login" %}  <span class="fa fa-sign-in"></span></a>
 | 
				
			||||||
      {% else %}
 | 
					          </li>
 | 
				
			||||||
        <li>
 | 
					        {% else %}
 | 
				
			||||||
          <a href="{% url 'hosting:dashboard' %}">{% trans "Dashboard" %}</a>
 | 
					          <li>
 | 
				
			||||||
        </li>
 | 
					            <a href="{% url 'hosting:dashboard' %}">{% trans "Dashboard" %}</a>
 | 
				
			||||||
 | 
					          </li>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
      {% endif %}
 | 
					      {% endif %}
 | 
				
			||||||
      {% comment %}
 | 
					      {% comment %}
 | 
				
			||||||
      <!-- to be used when more than one option for language -->
 | 
					      <!-- to be used when more than one option for language -->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,11 +9,14 @@
 | 
				
			||||||
        window.ssdUnitPrice = {{vm_pricing.ssd_unit_price|default:0}};
 | 
					        window.ssdUnitPrice = {{vm_pricing.ssd_unit_price|default:0}};
 | 
				
			||||||
        window.hddUnitPrice = {{vm_pricing.hdd_unit_price|default:0}};
 | 
					        window.hddUnitPrice = {{vm_pricing.hdd_unit_price|default:0}};
 | 
				
			||||||
        window.discountAmount = {{vm_pricing.discount_amount|default:0}};
 | 
					        window.discountAmount = {{vm_pricing.discount_amount|default:0}};
 | 
				
			||||||
 | 
					        window.minRam = {{min_ram}};
 | 
				
			||||||
 | 
					        window.minRamErr = '{% blocktrans with min_ram=min_ram %}Please enter a value in range {{min_ram}} - 200.{% endblocktrans %}';
 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
{% endif %}
 | 
					{% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<form id="order_form" method="POST" action="{{calculator_form_url}}" data-toggle="validator" role="form">
 | 
					<form id="order_form" method="POST" action="{{calculator_form_url}}" data-toggle="validator" role="form">
 | 
				
			||||||
    {% csrf_token %}
 | 
					    {% csrf_token %}
 | 
				
			||||||
 | 
					    <input type="hidden" name="pid" value="{{instance.id}}">
 | 
				
			||||||
    <div class="title">
 | 
					    <div class="title">
 | 
				
			||||||
        <h3>{% trans "VM hosting" %} </h3>
 | 
					        <h3>{% trans "VM hosting" %} </h3>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
| 
						 | 
					@ -54,8 +57,8 @@
 | 
				
			||||||
        <div class="form-group">
 | 
					        <div class="form-group">
 | 
				
			||||||
            <div class="description input">
 | 
					            <div class="description input">
 | 
				
			||||||
                <i class="fa fa-minus-circle left" data-minus="ram" aria-hidden="true"></i>
 | 
					                <i class="fa fa-minus-circle left" data-minus="ram" aria-hidden="true"></i>
 | 
				
			||||||
                <input id="ramValue" class="input-price select-number" type="number" min="1" max="200" name="ram"
 | 
					                <input id="ramValue" class="input-price select-number" type="number" min="{% if min_ram == 0.5 %}0{% else %}1{% endif %}" max="200" name="ram"
 | 
				
			||||||
                       data-error="{% trans 'Please enter a value in range 1 - 200.' %}" required>
 | 
					                       data-error="{% blocktrans with min_ram=min_ram %}Please enter a value in range {{min_ram}} - 200.{% endblocktrans %}" required step="1">
 | 
				
			||||||
                <span> GB RAM</span>
 | 
					                <span> GB RAM</span>
 | 
				
			||||||
                <i class="fa fa-plus-circle right" data-plus="ram" aria-hidden="true"></i>
 | 
					                <i class="fa fa-plus-circle right" data-plus="ram" aria-hidden="true"></i>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
| 
						 | 
					@ -91,11 +94,12 @@
 | 
				
			||||||
            <label for="config">OS</label>
 | 
					            <label for="config">OS</label>
 | 
				
			||||||
            <select name="config">
 | 
					            <select name="config">
 | 
				
			||||||
                {% for template in templates %}
 | 
					                {% for template in templates %}
 | 
				
			||||||
                <option value="{{template.opennebula_vm_template_id}}">{{template.name}}</option>
 | 
					
 | 
				
			||||||
 | 
					                <option value="{{template.opennebula_vm_template_id}}" {% if template.name|lower == instance.default_selected_template|lower %}selected="selected"{% endif %}>{{template.name}}</option>
 | 
				
			||||||
                {% endfor %}
 | 
					                {% endfor %}
 | 
				
			||||||
            </select>
 | 
					            </select>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <input type="hidden" name="pricing_name" value="{% if vm_pricing.name %}{{vm_pricing.name}}{% else %}unknown{% endif%}"></input>
 | 
					    <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>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,36 +67,49 @@
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="dcl-payment-box">
 | 
					            <div class="dcl-payment-box">
 | 
				
			||||||
                <div class="dcl-payment-section">
 | 
					                <div class="dcl-payment-section">
 | 
				
			||||||
                    <h3>{%trans "Your Order" %}</h3>
 | 
					                    {% if generic_payment_form %}
 | 
				
			||||||
                    <hr class="top-hr">
 | 
					                        <h3>{%trans "Make a payment" %}</h3>
 | 
				
			||||||
                    <div class="dcl-payment-order">
 | 
					                        <hr class="top-hr">
 | 
				
			||||||
                        <p>{% trans "Cores"%} <strong class="pull-right">{{request.session.specs.cpu|floatformat}}</strong></p>
 | 
					                        <form role="form" id="generic-payment-form" method="post" action="" novalidate>
 | 
				
			||||||
                        <hr>
 | 
					                            {% csrf_token %}
 | 
				
			||||||
                        <p>{% trans "Memory"%} <strong class="pull-right">{{request.session.specs.memory|floatformat}} GB</strong></p>
 | 
					                            <input type="hidden" name="product" value="1" />
 | 
				
			||||||
                        <hr>
 | 
					                            {% for field in generic_payment_form %}
 | 
				
			||||||
                        <p>{% trans "Disk space"%} <strong class="pull-right">{{request.session.specs.disk_size|floatformat}} GB</strong></p>
 | 
					                            {% bootstrap_field field type='fields'%}
 | 
				
			||||||
                        <hr>
 | 
					                            {% endfor %}
 | 
				
			||||||
                        <p>{% trans "Configuration"%} <strong class="pull-right">{{request.session.template.name}}</strong></p>
 | 
					                            <p class="text-danger">{{generic_payment_form.non_field_errors|striptags}}</p>
 | 
				
			||||||
                        <hr>
 | 
					                        </form>
 | 
				
			||||||
                        <p>
 | 
					                    {% else %}
 | 
				
			||||||
                            <strong>{%trans "Total" %}</strong>  
 | 
					                        <h3>{%trans "Your Order" %}</h3>
 | 
				
			||||||
                            <small>
 | 
					                        <hr class="top-hr">
 | 
				
			||||||
                                ({% if vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %})
 | 
					                        <div class="dcl-payment-order">
 | 
				
			||||||
                            </small>
 | 
					                            <p>{% trans "Cores"%} <strong class="pull-right">{{request.session.specs.cpu|floatformat}}</strong></p>
 | 
				
			||||||
                            <strong class="pull-right">{{request.session.specs.price|intcomma}} CHF/{% trans "Month" %}</strong>
 | 
					                            <hr>
 | 
				
			||||||
                        </p>
 | 
					                            <p>{% trans "Memory"%} <strong class="pull-right">{{request.session.specs.memory|floatformat}} GB</strong></p>
 | 
				
			||||||
                        <hr>
 | 
					                            <hr>
 | 
				
			||||||
                        {% if vm_pricing.discount_amount %}
 | 
					                            <p>{% trans "Disk space"%} <strong class="pull-right">{{request.session.specs.disk_size|floatformat}} GB</strong></p>
 | 
				
			||||||
                        <p class="mb-0">
 | 
					                            <hr>
 | 
				
			||||||
                            {%trans "Discount" as discount_name %}
 | 
					                            <p>{% trans "Configuration"%} <strong class="pull-right">{{request.session.template.name}}</strong></p>
 | 
				
			||||||
                            <strong>{{ vm_pricing.discount_name|default:discount_name }}</strong>  
 | 
					                            <hr>
 | 
				
			||||||
                            <strong class="pull-right text-primary">- {{ vm_pricing.discount_amount }} CHF/{% trans "Month" %}</strong>
 | 
					                            <p>
 | 
				
			||||||
                        </p>
 | 
					                                <strong>{%trans "Total" %}</strong>  
 | 
				
			||||||
                        <p>
 | 
					                                <small>
 | 
				
			||||||
                            ({% trans "Will be applied at checkout" %})
 | 
					                                    ({% if vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %})
 | 
				
			||||||
                        </p>
 | 
					                                </small>
 | 
				
			||||||
                        {% endif %}
 | 
					                                <strong class="pull-right">{{request.session.specs.price|intcomma}} CHF/{% trans "Month" %}</strong>
 | 
				
			||||||
                    </div>
 | 
					                            </p>
 | 
				
			||||||
 | 
					                            <hr>
 | 
				
			||||||
 | 
					                            {% if vm_pricing.discount_amount %}
 | 
				
			||||||
 | 
					                            <p class="mb-0">
 | 
				
			||||||
 | 
					                                {%trans "Discount" as discount_name %}
 | 
				
			||||||
 | 
					                                <strong>{{ vm_pricing.discount_name|default:discount_name }}</strong>  
 | 
				
			||||||
 | 
					                                <strong class="pull-right text-primary">- {{ vm_pricing.discount_amount }} CHF/{% trans "Month" %}</strong>
 | 
				
			||||||
 | 
					                            </p>
 | 
				
			||||||
 | 
					                            <p>
 | 
				
			||||||
 | 
					                                ({% trans "Will be applied at checkout" %})
 | 
				
			||||||
 | 
					                            </p>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="dcl-payment-box">
 | 
					            <div class="dcl-payment-box">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -47,61 +47,88 @@
 | 
				
			||||||
            <hr>
 | 
					            <hr>
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                <h4>{% trans "Order summary" %}</h4>
 | 
					                <h4>{% trans "Order summary" %}</h4>
 | 
				
			||||||
                <p>
 | 
					                    {% if generic_payment_details %}
 | 
				
			||||||
                    <strong>{% trans "Product" %}:</strong> 
 | 
					 | 
				
			||||||
                    {{ request.session.template.name }}
 | 
					 | 
				
			||||||
                </p>
 | 
					 | 
				
			||||||
                <div class="row">
 | 
					 | 
				
			||||||
                    <div class="col-sm-6">
 | 
					 | 
				
			||||||
                        <p>
 | 
					                        <p>
 | 
				
			||||||
                            <span>{% trans "Cores" %}: </span>
 | 
					                            <strong>{% trans "Product" %}:</strong> 
 | 
				
			||||||
                            <strong class="pull-right">{{vm.cpu|floatformat}}</strong>
 | 
					                            {{ generic_payment_details.product_name }}
 | 
				
			||||||
                        </p>
 | 
					                        </p>
 | 
				
			||||||
                        <p>
 | 
					                        <div class="row">
 | 
				
			||||||
                            <span>{% trans "Memory" %}: </span>
 | 
					                            <div class="col-sm-6">
 | 
				
			||||||
                            <strong class="pull-right">{{vm.memory|intcomma}} GB</strong>
 | 
					                                <p>
 | 
				
			||||||
                        </p>
 | 
					                                    <span>{% trans "Amount" %}: </span>
 | 
				
			||||||
                        <p>
 | 
					                                    <strong class="pull-right">CHF {{generic_payment_details.amount|floatformat:2|intcomma}}</strong>
 | 
				
			||||||
                            <span>{% trans "Disk space" %}: </span>
 | 
					                                </p>
 | 
				
			||||||
                            <strong class="pull-right">{{vm.disk_size|intcomma}} GB</strong>
 | 
					                                {% if generic_payment_details.description %}
 | 
				
			||||||
                        </p>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <div class="col-sm-12">
 | 
					 | 
				
			||||||
                        <hr class="thin-hr">
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    {% if vm.vat > 0 or vm.discount.amount > 0 %}
 | 
					 | 
				
			||||||
                        <div class="col-sm-6">
 | 
					 | 
				
			||||||
                            <div class="subtotal-price">
 | 
					 | 
				
			||||||
                                {% if vm.vat > 0 %}
 | 
					 | 
				
			||||||
                                    <p>
 | 
					                                    <p>
 | 
				
			||||||
                                        <strong class="text-lg">{% trans "Subtotal" %} </strong>
 | 
					                                        <span>{% trans "Description" %}: </span>
 | 
				
			||||||
                                        <strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
 | 
					                                        <strong class="pull-right">{{generic_payment_details.description}}</strong>
 | 
				
			||||||
                                    </p>
 | 
					 | 
				
			||||||
                                    <p>
 | 
					 | 
				
			||||||
                                        <small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) </small>
 | 
					 | 
				
			||||||
                                        <strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
 | 
					 | 
				
			||||||
                                    </p>
 | 
					                                    </p>
 | 
				
			||||||
                                {% endif %}
 | 
					                                {% endif %}
 | 
				
			||||||
                                {% if vm.discount.amount > 0 %}
 | 
					                                {% if generic_payment_details.recurring %}
 | 
				
			||||||
                                    <p class="text-primary">
 | 
					                                    <p>
 | 
				
			||||||
                                        {%trans "Discount" as discount_name %}
 | 
					                                        <span>{% trans "Recurring" %}: </span>
 | 
				
			||||||
                                        <strong>{{ vm.discount.name|default:discount_name }} </strong>
 | 
					                                        <strong class="pull-right">Yes</strong>
 | 
				
			||||||
                                        <strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
 | 
					 | 
				
			||||||
                                    </p>
 | 
					                                    </p>
 | 
				
			||||||
                                {% endif %}
 | 
					                                {% endif %}
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                        <div class="col-sm-12">
 | 
					                    {% else %}
 | 
				
			||||||
                            <hr class="thin-hr">
 | 
					                        <p>
 | 
				
			||||||
 | 
					                            <strong>{% trans "Product" %}:</strong> 
 | 
				
			||||||
 | 
					                            {{ request.session.template.name }}
 | 
				
			||||||
 | 
					                        </p>
 | 
				
			||||||
 | 
					                        <div class="row">
 | 
				
			||||||
 | 
					                            <div class="col-sm-6">
 | 
				
			||||||
 | 
					                                <p>
 | 
				
			||||||
 | 
					                                    <span>{% trans "Cores" %}: </span>
 | 
				
			||||||
 | 
					                                    <strong class="pull-right">{{vm.cpu|floatformat}}</strong>
 | 
				
			||||||
 | 
					                                </p>
 | 
				
			||||||
 | 
					                                <p>
 | 
				
			||||||
 | 
					                                    <span>{% trans "Memory" %}: </span>
 | 
				
			||||||
 | 
					                                    <strong class="pull-right">{{vm.memory|intcomma}} GB</strong>
 | 
				
			||||||
 | 
					                                </p>
 | 
				
			||||||
 | 
					                                <p>
 | 
				
			||||||
 | 
					                                    <span>{% trans "Disk space" %}: </span>
 | 
				
			||||||
 | 
					                                    <strong class="pull-right">{{vm.disk_size|intcomma}} GB</strong>
 | 
				
			||||||
 | 
					                                </p>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <div class="col-sm-12">
 | 
				
			||||||
 | 
					                                <hr class="thin-hr">
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            {% if vm.vat > 0 or vm.discount.amount > 0 %}
 | 
				
			||||||
 | 
					                                <div class="col-sm-6">
 | 
				
			||||||
 | 
					                                    <div class="subtotal-price">
 | 
				
			||||||
 | 
					                                        {% if vm.vat > 0 %}
 | 
				
			||||||
 | 
					                                            <p>
 | 
				
			||||||
 | 
					                                                <strong class="text-lg">{% trans "Subtotal" %} </strong>
 | 
				
			||||||
 | 
					                                                <strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
 | 
				
			||||||
 | 
					                                            </p>
 | 
				
			||||||
 | 
					                                            <p>
 | 
				
			||||||
 | 
					                                                <small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) </small>
 | 
				
			||||||
 | 
					                                                <strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
 | 
				
			||||||
 | 
					                                            </p>
 | 
				
			||||||
 | 
					                                        {% endif %}
 | 
				
			||||||
 | 
					                                        {% if vm.discount.amount > 0 %}
 | 
				
			||||||
 | 
					                                            <p class="text-primary">
 | 
				
			||||||
 | 
					                                                {%trans "Discount" as discount_name %}
 | 
				
			||||||
 | 
					                                                <strong>{{ vm.discount.name|default:discount_name }} </strong>
 | 
				
			||||||
 | 
					                                                <strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
 | 
				
			||||||
 | 
					                                            </p>
 | 
				
			||||||
 | 
					                                        {% endif %}
 | 
				
			||||||
 | 
					                                    </div>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                                <div class="col-sm-12">
 | 
				
			||||||
 | 
					                                    <hr class="thin-hr">
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                            <div class="col-sm-6">
 | 
				
			||||||
 | 
					                                <p class="total-price">
 | 
				
			||||||
 | 
					                                    <strong>{% trans "Total" %} </strong>
 | 
				
			||||||
 | 
					                                    <strong class="pull-right">{{vm.total_price|floatformat:2|intcomma}} CHF</strong>
 | 
				
			||||||
 | 
					                                </p>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    {% endif %}
 | 
					                    {% endif %}
 | 
				
			||||||
                    <div class="col-sm-6">
 | 
					 | 
				
			||||||
                        <p class="total-price">
 | 
					 | 
				
			||||||
                            <strong>{% trans "Total" %} </strong>
 | 
					 | 
				
			||||||
                            <strong class="pull-right">{{vm.total_price|floatformat:2|intcomma}} CHF</strong>
 | 
					 | 
				
			||||||
                        </p>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <hr class="thin-hr">
 | 
					            <hr class="thin-hr">
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
| 
						 | 
					@ -109,7 +136,15 @@
 | 
				
			||||||
            {% 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_total_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{vm_total_price}} CHF/month{% endblocktrans %}.</div>
 | 
					                    {% if generic_payment_details %}
 | 
				
			||||||
 | 
					                        {% if generic_payment_details.recurring %}
 | 
				
			||||||
 | 
					                            <div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/month{% endblocktrans %}.</div>
 | 
				
			||||||
 | 
					                        {% else %}
 | 
				
			||||||
 | 
					                            <div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this payment will charge your credit card account with a one time amount of {{total_price}} CHF{% endblocktrans %}.</div>
 | 
				
			||||||
 | 
					                        {% endif %}
 | 
				
			||||||
 | 
					                    {% else %}
 | 
				
			||||||
 | 
					                        <div class="dcl-place-order-text">{% blocktrans with vm_total_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{vm_total_price}} CHF/month{% endblocktrans %}.</div>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="col-sm-4 order-confirm-btn text-right">
 | 
					                <div class="col-sm-4 order-confirm-btn text-right">
 | 
				
			||||||
                    <button class="btn choice-btn" id="btn-create-vm" data-toggle="modal" data-target="#createvm-modal">
 | 
					                    <button class="btn choice-btn" id="btn-create-vm" data-toggle="modal" data-target="#createvm-modal">
 | 
				
			||||||
| 
						 | 
					@ -151,16 +186,5 @@
 | 
				
			||||||
<script type="text/javascript">
 | 
					<script type="text/javascript">
 | 
				
			||||||
    {% trans "Some problem encountered. Please try again later." as err_msg %}
 | 
					    {% trans "Some problem encountered. Please try again later." as err_msg %}
 | 
				
			||||||
    var create_vm_error_message = '{{err_msg|safe}}';
 | 
					    var create_vm_error_message = '{{err_msg|safe}}';
 | 
				
			||||||
    window.onload = function () {
 | 
					 | 
				
			||||||
        var locale_dates = document.getElementsByClassName("locale_date");
 | 
					 | 
				
			||||||
        var formats = ['YYYY-MM-DD hh:mm a']
 | 
					 | 
				
			||||||
        var i;
 | 
					 | 
				
			||||||
        for (i = 0; i < locale_dates.length; i++) {
 | 
					 | 
				
			||||||
            var oldDate = moment.utc(locale_dates[i].textContent, formats);
 | 
					 | 
				
			||||||
            var outputFormat = locale_dates[i].getAttribute('data-format') || oldDate._f;
 | 
					 | 
				
			||||||
            locale_dates[i].innerHTML = oldDate.local().format(outputFormat);
 | 
					 | 
				
			||||||
            locale_dates[i].className += ' done';
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
{%endblock%}
 | 
					{%endblock%}
 | 
				
			||||||
| 
						 | 
					@ -105,7 +105,8 @@ class CeleryTaskTestCase(TestCase):
 | 
				
			||||||
                                            disk_size=disk_size)
 | 
					                                            disk_size=disk_size)
 | 
				
			||||||
        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,
 | 
				
			||||||
 | 
					                                                     price=amount_to_be_charged)
 | 
				
			||||||
        stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
 | 
					        stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
 | 
				
			||||||
                                                        ram=memory,
 | 
					                                                        ram=memory,
 | 
				
			||||||
                                                        ssd=disk_size,
 | 
					                                                        ssd=disk_size,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,8 +89,14 @@ def create_vm(billing_address_data, stripe_customer_id, specs,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    create_vm_task.delay(vm_template_id, user, specs, template, order.id)
 | 
					    create_vm_task.delay(vm_template_id, user, specs, template, order.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for session_var in ['specs', 'template', 'billing_address',
 | 
					    clear_all_session_vars(request)
 | 
				
			||||||
                        'billing_address_data', 'card_id',
 | 
					
 | 
				
			||||||
                        'token', 'customer']:
 | 
					
 | 
				
			||||||
        if session_var in request.session:
 | 
					def clear_all_session_vars(request):
 | 
				
			||||||
            del request.session[session_var]
 | 
					    if request.session is not None:
 | 
				
			||||||
 | 
					        for session_var in ['specs', 'template', 'billing_address',
 | 
				
			||||||
 | 
					                            'billing_address_data', 'card_id',
 | 
				
			||||||
 | 
					                            'token', 'customer', 'generic_payment_type',
 | 
				
			||||||
 | 
					                            'generic_payment_details', 'product_id']:
 | 
				
			||||||
 | 
					            if session_var in request.session:
 | 
				
			||||||
 | 
					                del request.session[session_var]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,23 +6,31 @@ from django.contrib import messages
 | 
				
			||||||
from django.contrib.auth import login, authenticate
 | 
					from django.contrib.auth import login, authenticate
 | 
				
			||||||
from django.core.exceptions import ValidationError
 | 
					from django.core.exceptions import ValidationError
 | 
				
			||||||
from django.core.urlresolvers import reverse
 | 
					from django.core.urlresolvers import reverse
 | 
				
			||||||
from django.http import HttpResponseRedirect, JsonResponse
 | 
					from django.http import HttpResponseRedirect, JsonResponse, Http404
 | 
				
			||||||
from django.shortcuts import render
 | 
					from django.shortcuts import render
 | 
				
			||||||
from django.utils.translation import get_language, ugettext_lazy as _
 | 
					from django.utils.translation import get_language, ugettext_lazy as _
 | 
				
			||||||
from django.views.decorators.cache import cache_control
 | 
					from django.views.decorators.cache import cache_control
 | 
				
			||||||
from django.views.generic import FormView, CreateView, DetailView
 | 
					from django.views.generic import FormView, CreateView, DetailView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from hosting.forms import HostingUserLoginForm
 | 
					from hosting.forms import (
 | 
				
			||||||
from hosting.models import HostingOrder, UserCardDetail
 | 
					    HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from hosting.models import (
 | 
				
			||||||
 | 
					    HostingBill, HostingOrder, UserCardDetail, GenericProduct
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from membership.models import CustomUser, StripeCustomer
 | 
					from membership.models import CustomUser, StripeCustomer
 | 
				
			||||||
from opennebula_api.serializers import VMTemplateSerializer
 | 
					from opennebula_api.serializers import VMTemplateSerializer
 | 
				
			||||||
from utils.forms import BillingAddressForm, BillingAddressFormSignup
 | 
					from utils.forms import (
 | 
				
			||||||
 | 
					    BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm,
 | 
				
			||||||
 | 
					    BillingAddress
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
from utils.hosting_utils import get_vm_price_with_vat
 | 
					from utils.hosting_utils import get_vm_price_with_vat
 | 
				
			||||||
from utils.stripe_utils import StripeUtils
 | 
					from utils.stripe_utils import StripeUtils
 | 
				
			||||||
from utils.tasks import send_plain_email_task
 | 
					from utils.tasks import send_plain_email_task
 | 
				
			||||||
 | 
					from .cms_models import DCLCalculatorPluginModel
 | 
				
			||||||
from .forms import ContactForm
 | 
					from .forms import ContactForm
 | 
				
			||||||
from .models import VMTemplate, VMPricing
 | 
					from .models import VMTemplate, VMPricing
 | 
				
			||||||
from .utils import get_cms_integration, create_vm
 | 
					from .utils import get_cms_integration, create_vm, clear_all_session_vars
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger(__name__)
 | 
					logger = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -82,7 +90,29 @@ class IndexView(CreateView):
 | 
				
			||||||
            raise ValidationError(_('Invalid number of cores'))
 | 
					            raise ValidationError(_('Invalid number of cores'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def validate_memory(self, value):
 | 
					    def validate_memory(self, value):
 | 
				
			||||||
        if (value > 200) or (value < 1):
 | 
					        if 'pid' in self.request.POST:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                plugin = DCLCalculatorPluginModel.objects.get(
 | 
				
			||||||
 | 
					                             id=self.request.POST['pid']
 | 
				
			||||||
 | 
					                         )
 | 
				
			||||||
 | 
					            except DCLCalculatorPluginModel.DoesNotExist as dne:
 | 
				
			||||||
 | 
					                logger.error(
 | 
				
			||||||
 | 
					                    str(dne) + " plugin_id: " + self.request.POST['pid']
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                raise ValidationError(_('Invalid calculator properties'))
 | 
				
			||||||
 | 
					            if plugin.enable_512mb_ram:
 | 
				
			||||||
 | 
					                if value % 1 == 0 or value == 0.5:
 | 
				
			||||||
 | 
					                    logger.debug(
 | 
				
			||||||
 | 
					                        "Given ram {value} is either 0.5 or a"
 | 
				
			||||||
 | 
					                        " whole number".format(value=value)
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    if (value > 200) or (value < 0.5):
 | 
				
			||||||
 | 
					                        raise ValidationError(_('Invalid RAM size'))
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    raise ValidationError(_('Invalid RAM size'))
 | 
				
			||||||
 | 
					            elif (value > 200) or (value < 1) or (value % 1 != 0):
 | 
				
			||||||
 | 
					                raise ValidationError(_('Invalid RAM size'))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
            raise ValidationError(_('Invalid RAM size'))
 | 
					            raise ValidationError(_('Invalid RAM size'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def validate_storage(self, value):
 | 
					    def validate_storage(self, value):
 | 
				
			||||||
| 
						 | 
					@ -91,17 +121,14 @@ 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',
 | 
					        clear_all_session_vars(request)
 | 
				
			||||||
                            'pricing_name']:
 | 
					 | 
				
			||||||
            if session_var in request.session:
 | 
					 | 
				
			||||||
                del request.session[session_var]
 | 
					 | 
				
			||||||
        return HttpResponseRedirect(reverse('datacenterlight:cms_index'))
 | 
					        return HttpResponseRedirect(reverse('datacenterlight:cms_index'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post(self, request):
 | 
					    def post(self, request):
 | 
				
			||||||
        cores = request.POST.get('cpu')
 | 
					        cores = request.POST.get('cpu')
 | 
				
			||||||
        cores_field = forms.IntegerField(validators=[self.validate_cores])
 | 
					        cores_field = forms.IntegerField(validators=[self.validate_cores])
 | 
				
			||||||
        memory = request.POST.get('ram')
 | 
					        memory = request.POST.get('ram')
 | 
				
			||||||
        memory_field = forms.IntegerField(validators=[self.validate_memory])
 | 
					        memory_field = forms.FloatField(validators=[self.validate_memory])
 | 
				
			||||||
        storage = request.POST.get('storage')
 | 
					        storage = request.POST.get('storage')
 | 
				
			||||||
        storage_field = forms.IntegerField(validators=[self.validate_storage])
 | 
					        storage_field = forms.IntegerField(validators=[self.validate_storage])
 | 
				
			||||||
        template_id = int(request.POST.get('config'))
 | 
					        template_id = int(request.POST.get('config'))
 | 
				
			||||||
| 
						 | 
					@ -170,7 +197,7 @@ class IndexView(CreateView):
 | 
				
			||||||
            'vat': vat,
 | 
					            'vat': vat,
 | 
				
			||||||
            'vat_percent': vat_percent,
 | 
					            'vat_percent': vat_percent,
 | 
				
			||||||
            'discount': discount,
 | 
					            'discount': discount,
 | 
				
			||||||
            'total_price': price + vat - discount['amount'],
 | 
					            'total_price': round(price + vat - discount['amount'], 2),
 | 
				
			||||||
            'pricing_name': vm_pricing_name
 | 
					            'pricing_name': vm_pricing_name
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        request.session['specs'] = specs
 | 
					        request.session['specs'] = specs
 | 
				
			||||||
| 
						 | 
					@ -242,19 +269,93 @@ class PaymentOrderView(FormView):
 | 
				
			||||||
            'login_form': HostingUserLoginForm(prefix='login_form'),
 | 
					            'login_form': HostingUserLoginForm(prefix='login_form'),
 | 
				
			||||||
            'billing_address_form': billing_address_form,
 | 
					            'billing_address_form': billing_address_form,
 | 
				
			||||||
            'cms_integration': get_cms_integration('default'),
 | 
					            'cms_integration': get_cms_integration('default'),
 | 
				
			||||||
            'vm_pricing': VMPricing.get_vm_pricing_by_name(
 | 
					 | 
				
			||||||
                self.request.session['specs']['pricing_name']
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ('generic_payment_type' in self.request.session and
 | 
				
			||||||
 | 
					                self.request.session['generic_payment_type'] == 'generic'):
 | 
				
			||||||
 | 
					            if 'product_id' in self.request.session:
 | 
				
			||||||
 | 
					                product = GenericProduct.objects.get(
 | 
				
			||||||
 | 
					                    id=self.request.session['product_id']
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                context.update({'generic_payment_form': ProductPaymentForm(
 | 
				
			||||||
 | 
					                    prefix='generic_payment_form',
 | 
				
			||||||
 | 
					                    initial={'product_name': product.product_name,
 | 
				
			||||||
 | 
					                             'amount': float(product.get_actual_price()),
 | 
				
			||||||
 | 
					                             'recurring': product.product_is_subscription,
 | 
				
			||||||
 | 
					                             'description': product.product_description,
 | 
				
			||||||
 | 
					                             },
 | 
				
			||||||
 | 
					                    product_id=product.id
 | 
				
			||||||
 | 
					                ), })
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                context.update({'generic_payment_form': GenericPaymentForm(
 | 
				
			||||||
 | 
					                    prefix='generic_payment_form',
 | 
				
			||||||
 | 
					                ), })
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            context.update({
 | 
				
			||||||
 | 
					                'vm_pricing': VMPricing.get_vm_pricing_by_name(
 | 
				
			||||||
 | 
					                    self.request.session['specs']['pricing_name']
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return context
 | 
					        return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @cache_control(no_cache=True, must_revalidate=True, no_store=True)
 | 
					    @cache_control(no_cache=True, must_revalidate=True, no_store=True)
 | 
				
			||||||
    def get(self, request, *args, **kwargs):
 | 
					    def get(self, request, *args, **kwargs):
 | 
				
			||||||
        if 'specs' not in request.session:
 | 
					        if (('type' in request.GET and request.GET['type'] == 'generic')
 | 
				
			||||||
 | 
					                or 'product_slug' in kwargs):
 | 
				
			||||||
 | 
					            request.session['generic_payment_type'] = 'generic'
 | 
				
			||||||
 | 
					            if 'generic_payment_details' in request.session:
 | 
				
			||||||
 | 
					                request.session.pop('generic_payment_details')
 | 
				
			||||||
 | 
					                request.session.pop('product_id')
 | 
				
			||||||
 | 
					            if 'product_slug' in kwargs:
 | 
				
			||||||
 | 
					                logger.debug("Product slug is " + kwargs['product_slug'])
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    product = GenericProduct.objects.get(
 | 
				
			||||||
 | 
					                        product_slug=kwargs['product_slug']
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                except GenericProduct.DoesNotExist as dne:
 | 
				
			||||||
 | 
					                    logger.error(
 | 
				
			||||||
 | 
					                        "Product '{}' does "
 | 
				
			||||||
 | 
					                        "not exist".format(kwargs['product_slug'])
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    raise Http404()
 | 
				
			||||||
 | 
					                request.session['product_id'] = product.id
 | 
				
			||||||
 | 
					        elif 'specs' not in request.session:
 | 
				
			||||||
            return HttpResponseRedirect(reverse('datacenterlight:index'))
 | 
					            return HttpResponseRedirect(reverse('datacenterlight:index'))
 | 
				
			||||||
        return self.render_to_response(self.get_context_data())
 | 
					        return self.render_to_response(self.get_context_data())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post(self, request, *args, **kwargs):
 | 
					    def post(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        if 'product' in request.POST:
 | 
				
			||||||
 | 
					            # query for the supplied product
 | 
				
			||||||
 | 
					            product = None
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                product = GenericProduct.objects.get(
 | 
				
			||||||
 | 
					                    id=request.POST['generic_payment_form-product_name']
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            except GenericProduct.DoesNotExist as dne:
 | 
				
			||||||
 | 
					                logger.error(
 | 
				
			||||||
 | 
					                    "The requested product '{}' does not exist".format(
 | 
				
			||||||
 | 
					                        request.POST['generic_payment_form-product_name']
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            except GenericProduct.MultipleObjectsReturned as mpe:
 | 
				
			||||||
 | 
					                logger.error(
 | 
				
			||||||
 | 
					                    "There seem to be more than one product with "
 | 
				
			||||||
 | 
					                    "the name {}".format(
 | 
				
			||||||
 | 
					                        request.POST['generic_payment_form-product_name']
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                product = GenericProduct.objects.all(
 | 
				
			||||||
 | 
					                    product_name=request.
 | 
				
			||||||
 | 
					                    POST['generic_payment_form-product_name']
 | 
				
			||||||
 | 
					                ).first()
 | 
				
			||||||
 | 
					            if product is None:
 | 
				
			||||||
 | 
					                return JsonResponse({})
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                return JsonResponse({
 | 
				
			||||||
 | 
					                    'amount': product.get_actual_price(),
 | 
				
			||||||
 | 
					                    'isSubscription': product.product_is_subscription
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
        if 'login_form' in request.POST:
 | 
					        if 'login_form' in request.POST:
 | 
				
			||||||
            login_form = HostingUserLoginForm(
 | 
					            login_form = HostingUserLoginForm(
 | 
				
			||||||
                data=request.POST, prefix='login_form'
 | 
					                data=request.POST, prefix='login_form'
 | 
				
			||||||
| 
						 | 
					@ -265,6 +366,13 @@ class PaymentOrderView(FormView):
 | 
				
			||||||
                auth_user = authenticate(email=email, password=password)
 | 
					                auth_user = authenticate(email=email, password=password)
 | 
				
			||||||
                if auth_user:
 | 
					                if auth_user:
 | 
				
			||||||
                    login(self.request, auth_user)
 | 
					                    login(self.request, auth_user)
 | 
				
			||||||
 | 
					                    if 'product_slug' in kwargs:
 | 
				
			||||||
 | 
					                        return HttpResponseRedirect(
 | 
				
			||||||
 | 
					                            reverse('show_product',
 | 
				
			||||||
 | 
					                                    kwargs={
 | 
				
			||||||
 | 
					                                        'product_slug': kwargs['product_slug']}
 | 
				
			||||||
 | 
					                                    )
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
                    return HttpResponseRedirect(
 | 
					                    return HttpResponseRedirect(
 | 
				
			||||||
                        reverse('datacenterlight:payment')
 | 
					                        reverse('datacenterlight:payment')
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
| 
						 | 
					@ -281,6 +389,50 @@ class PaymentOrderView(FormView):
 | 
				
			||||||
                data=request.POST,
 | 
					                data=request.POST,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        if address_form.is_valid():
 | 
					        if address_form.is_valid():
 | 
				
			||||||
 | 
					            # Check if we are in a generic payment case and handle the generic
 | 
				
			||||||
 | 
					            # payment details form before we go on to verify payment
 | 
				
			||||||
 | 
					            if ('generic_payment_type' in request.session and
 | 
				
			||||||
 | 
					                    self.request.session['generic_payment_type'] == 'generic'):
 | 
				
			||||||
 | 
					                if 'product_id' in request.session:
 | 
				
			||||||
 | 
					                    generic_payment_form = ProductPaymentForm(
 | 
				
			||||||
 | 
					                        data=request.POST, prefix='generic_payment_form',
 | 
				
			||||||
 | 
					                        product_id=request.session['product_id']
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    generic_payment_form = GenericPaymentForm(
 | 
				
			||||||
 | 
					                        data=request.POST, prefix='generic_payment_form'
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                if generic_payment_form.is_valid():
 | 
				
			||||||
 | 
					                    logger.debug("Generic payment form is valid.")
 | 
				
			||||||
 | 
					                    if 'product_id' in request.session:
 | 
				
			||||||
 | 
					                        product = generic_payment_form.product
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        product = generic_payment_form.cleaned_data.get(
 | 
				
			||||||
 | 
					                            'product_name'
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    gp_details = {
 | 
				
			||||||
 | 
					                        "product_name": product.product_name,
 | 
				
			||||||
 | 
					                        "amount": generic_payment_form.cleaned_data.get(
 | 
				
			||||||
 | 
					                            'amount'
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        "recurring": generic_payment_form.cleaned_data.get(
 | 
				
			||||||
 | 
					                            'recurring'
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        "description": generic_payment_form.cleaned_data.get(
 | 
				
			||||||
 | 
					                            'description'
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        "product_id": product.id,
 | 
				
			||||||
 | 
					                        "product_slug": product.product_slug
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    request.session["generic_payment_details"] = (
 | 
				
			||||||
 | 
					                        gp_details
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    logger.debug("Generic payment form invalid")
 | 
				
			||||||
 | 
					                    context = self.get_context_data()
 | 
				
			||||||
 | 
					                    context['generic_payment_form'] = generic_payment_form
 | 
				
			||||||
 | 
					                    context['billing_address_form'] = address_form
 | 
				
			||||||
 | 
					                    return self.render_to_response(context)
 | 
				
			||||||
            token = address_form.cleaned_data.get('token')
 | 
					            token = address_form.cleaned_data.get('token')
 | 
				
			||||||
            if token is '':
 | 
					            if token is '':
 | 
				
			||||||
                card_id = address_form.cleaned_data.get('card')
 | 
					                card_id = address_form.cleaned_data.get('card')
 | 
				
			||||||
| 
						 | 
					@ -296,8 +448,8 @@ class PaymentOrderView(FormView):
 | 
				
			||||||
                except UserCardDetail.DoesNotExist as e:
 | 
					                except UserCardDetail.DoesNotExist as e:
 | 
				
			||||||
                    ex = str(e)
 | 
					                    ex = str(e)
 | 
				
			||||||
                    logger.error("Card Id: {card_id}, Exception: {ex}".format(
 | 
					                    logger.error("Card Id: {card_id}, Exception: {ex}".format(
 | 
				
			||||||
                            card_id=card_id, ex=ex
 | 
					                        card_id=card_id, ex=ex
 | 
				
			||||||
                        )
 | 
					                    )
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    msg = _("An error occurred. Details: {}".format(ex))
 | 
					                    msg = _("An error occurred. Details: {}".format(ex))
 | 
				
			||||||
                    messages.add_message(
 | 
					                    messages.add_message(
 | 
				
			||||||
| 
						 | 
					@ -386,7 +538,8 @@ class OrderConfirmationView(DetailView):
 | 
				
			||||||
    @cache_control(no_cache=True, must_revalidate=True, no_store=True)
 | 
					    @cache_control(no_cache=True, must_revalidate=True, no_store=True)
 | 
				
			||||||
    def get(self, request, *args, **kwargs):
 | 
					    def get(self, request, *args, **kwargs):
 | 
				
			||||||
        context = {}
 | 
					        context = {}
 | 
				
			||||||
        if 'specs' not in request.session or 'user' not in request.session:
 | 
					        if (('specs' not in request.session or 'user' not in request.session)
 | 
				
			||||||
 | 
					                and 'generic_payment_type' not in request.session):
 | 
				
			||||||
            return HttpResponseRedirect(reverse('datacenterlight:index'))
 | 
					            return HttpResponseRedirect(reverse('datacenterlight:index'))
 | 
				
			||||||
        if 'token' in self.request.session:
 | 
					        if 'token' in self.request.session:
 | 
				
			||||||
            token = self.request.session['token']
 | 
					            token = self.request.session['token']
 | 
				
			||||||
| 
						 | 
					@ -404,9 +557,19 @@ class OrderConfirmationView(DetailView):
 | 
				
			||||||
            card_detail = UserCardDetail.objects.get(id=card_id)
 | 
					            card_detail = UserCardDetail.objects.get(id=card_id)
 | 
				
			||||||
            context['cc_last4'] = card_detail.last4
 | 
					            context['cc_last4'] = card_detail.last4
 | 
				
			||||||
            context['cc_brand'] = card_detail.brand
 | 
					            context['cc_brand'] = card_detail.brand
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ('generic_payment_type' in request.session and
 | 
				
			||||||
 | 
					                self.request.session['generic_payment_type'] == 'generic'):
 | 
				
			||||||
 | 
					            context.update({
 | 
				
			||||||
 | 
					                'generic_payment_details':
 | 
				
			||||||
 | 
					                    request.session['generic_payment_details'],
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            context.update({
 | 
				
			||||||
 | 
					                'vm': request.session.get('specs'),
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
        context.update({
 | 
					        context.update({
 | 
				
			||||||
            'site_url': reverse('datacenterlight:index'),
 | 
					            'site_url': reverse('datacenterlight:index'),
 | 
				
			||||||
            'vm': request.session.get('specs'),
 | 
					 | 
				
			||||||
            'page_header_text': _('Confirm Order'),
 | 
					            'page_header_text': _('Confirm Order'),
 | 
				
			||||||
            'billing_address_data': (
 | 
					            'billing_address_data': (
 | 
				
			||||||
                request.session.get('billing_address_data')
 | 
					                request.session.get('billing_address_data')
 | 
				
			||||||
| 
						 | 
					@ -416,11 +579,8 @@ class OrderConfirmationView(DetailView):
 | 
				
			||||||
        return render(request, self.template_name, context)
 | 
					        return render(request, self.template_name, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post(self, request, *args, **kwargs):
 | 
					    def post(self, request, *args, **kwargs):
 | 
				
			||||||
        template = request.session.get('template')
 | 
					 | 
				
			||||||
        specs = request.session.get('specs')
 | 
					 | 
				
			||||||
        user = request.session.get('user')
 | 
					        user = request.session.get('user')
 | 
				
			||||||
        stripe_api_cus_id = request.session.get('customer')
 | 
					        stripe_api_cus_id = request.session.get('customer')
 | 
				
			||||||
        vm_template_id = template.get('id', 1)
 | 
					 | 
				
			||||||
        stripe_utils = StripeUtils()
 | 
					        stripe_utils = StripeUtils()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if 'token' in request.session:
 | 
					        if 'token' in request.session:
 | 
				
			||||||
| 
						 | 
					@ -434,7 +594,14 @@ class OrderConfirmationView(DetailView):
 | 
				
			||||||
                response = {
 | 
					                response = {
 | 
				
			||||||
                    'status': False,
 | 
					                    'status': False,
 | 
				
			||||||
                    'redirect': "{url}#{section}".format(
 | 
					                    'redirect': "{url}#{section}".format(
 | 
				
			||||||
                        url=reverse('datacenterlight:payment'),
 | 
					                        url=(reverse(
 | 
				
			||||||
 | 
					                            'show_product',
 | 
				
			||||||
 | 
					                            kwargs={'product_slug':
 | 
				
			||||||
 | 
					                                    request.session['generic_payment_details']
 | 
				
			||||||
 | 
					                                    ['product_slug']}
 | 
				
			||||||
 | 
					                        ) if 'generic_payment_details' in request.session else
 | 
				
			||||||
 | 
					                                reverse('datacenterlight:payment')
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
                        section='payment_error'),
 | 
					                        section='payment_error'),
 | 
				
			||||||
                    'msg_title': str(_('Error.')),
 | 
					                    'msg_title': str(_('Error.')),
 | 
				
			||||||
                    'msg_body': str(
 | 
					                    'msg_body': str(
 | 
				
			||||||
| 
						 | 
					@ -450,7 +617,8 @@ class OrderConfirmationView(DetailView):
 | 
				
			||||||
                'brand': card_details_response['brand'],
 | 
					                'brand': card_details_response['brand'],
 | 
				
			||||||
                'card_id': card_details_response['card_id']
 | 
					                'card_id': card_details_response['card_id']
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            stripe_customer_obj = StripeCustomer.objects.filter(stripe_id=stripe_api_cus_id).first()
 | 
					            stripe_customer_obj = StripeCustomer.objects.filter(
 | 
				
			||||||
 | 
					                stripe_id=stripe_api_cus_id).first()
 | 
				
			||||||
            if stripe_customer_obj:
 | 
					            if stripe_customer_obj:
 | 
				
			||||||
                ucd = UserCardDetail.get_user_card_details(
 | 
					                ucd = UserCardDetail.get_user_card_details(
 | 
				
			||||||
                    stripe_customer_obj, card_details_response
 | 
					                    stripe_customer_obj, card_details_response
 | 
				
			||||||
| 
						 | 
					@ -472,7 +640,16 @@ class OrderConfirmationView(DetailView):
 | 
				
			||||||
                        response = {
 | 
					                        response = {
 | 
				
			||||||
                            'status': False,
 | 
					                            'status': False,
 | 
				
			||||||
                            'redirect': "{url}#{section}".format(
 | 
					                            'redirect': "{url}#{section}".format(
 | 
				
			||||||
                                url=reverse('hosting:payment'),
 | 
					                                url=(reverse(
 | 
				
			||||||
 | 
					                                    'show_product',
 | 
				
			||||||
 | 
					                                    kwargs={'product_slug':
 | 
				
			||||||
 | 
					                                            request.session
 | 
				
			||||||
 | 
					                                            ['generic_payment_details']
 | 
				
			||||||
 | 
					                                            ['product_slug']}
 | 
				
			||||||
 | 
					                                    ) if 'generic_payment_details' in
 | 
				
			||||||
 | 
					                                     request.session else
 | 
				
			||||||
 | 
					                                     reverse('datacenterlight:payment')
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
                                section='payment_error'),
 | 
					                                section='payment_error'),
 | 
				
			||||||
                            'msg_title': str(_('Error.')),
 | 
					                            'msg_title': str(_('Error.')),
 | 
				
			||||||
                            'msg_body': str(
 | 
					                            'msg_body': str(
 | 
				
			||||||
| 
						 | 
					@ -504,51 +681,122 @@ class OrderConfirmationView(DetailView):
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return JsonResponse(response)
 | 
					            return JsonResponse(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cpu = specs.get('cpu')
 | 
					        if ('generic_payment_type' in request.session and
 | 
				
			||||||
        memory = specs.get('memory')
 | 
					                self.request.session['generic_payment_type'] == 'generic'):
 | 
				
			||||||
        disk_size = specs.get('disk_size')
 | 
					            gp_details = self.request.session['generic_payment_details']
 | 
				
			||||||
        amount_to_be_charged = specs.get('total_price')
 | 
					            if gp_details['recurring']:
 | 
				
			||||||
        plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
 | 
					                # generic recurring payment
 | 
				
			||||||
                                                     memory=memory,
 | 
					                logger.debug("Commencing a generic recurring payment")
 | 
				
			||||||
                                                     disk_size=disk_size)
 | 
					            else:
 | 
				
			||||||
        stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
 | 
					                # generic one time payment
 | 
				
			||||||
                                                        ram=memory,
 | 
					                logger.debug("Commencing a one time payment")
 | 
				
			||||||
                                                        ssd=disk_size,
 | 
					                charge_response = stripe_utils.make_charge(
 | 
				
			||||||
                                                        version=1,
 | 
					                    amount=gp_details['amount'],
 | 
				
			||||||
                                                        app='dcl')
 | 
					                    customer=stripe_api_cus_id
 | 
				
			||||||
        stripe_plan = stripe_utils.get_or_create_stripe_plan(
 | 
					                )
 | 
				
			||||||
            amount=amount_to_be_charged,
 | 
					                stripe_onetime_charge = charge_response.get('response_object')
 | 
				
			||||||
            name=plan_name,
 | 
					 | 
				
			||||||
            stripe_plan_id=stripe_plan_id)
 | 
					 | 
				
			||||||
        subscription_result = stripe_utils.subscribe_customer_to_plan(
 | 
					 | 
				
			||||||
            stripe_api_cus_id,
 | 
					 | 
				
			||||||
            [{"plan": stripe_plan.get(
 | 
					 | 
				
			||||||
                'response_object').stripe_plan_id}])
 | 
					 | 
				
			||||||
        stripe_subscription_obj = subscription_result.get('response_object')
 | 
					 | 
				
			||||||
        # Check if the subscription was approved and is active
 | 
					 | 
				
			||||||
        if (stripe_subscription_obj is None
 | 
					 | 
				
			||||||
                or stripe_subscription_obj.status != 'active'):
 | 
					 | 
				
			||||||
            # At this point, we have created a Stripe API card and
 | 
					 | 
				
			||||||
            # associated it with the customer; but the transaction failed
 | 
					 | 
				
			||||||
            # due to some reason. So, we would want to dissociate this card
 | 
					 | 
				
			||||||
            # here.
 | 
					 | 
				
			||||||
            # ...
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            msg = subscription_result.get('error')
 | 
					                # Check if the payment was approved
 | 
				
			||||||
            messages.add_message(self.request, messages.ERROR, msg,
 | 
					                if not stripe_onetime_charge:
 | 
				
			||||||
                                 extra_tags='failed_payment')
 | 
					                    msg = charge_response.get('error')
 | 
				
			||||||
            response = {
 | 
					                    messages.add_message(self.request, messages.ERROR, msg,
 | 
				
			||||||
                'status': False,
 | 
					                                         extra_tags='failed_payment')
 | 
				
			||||||
                'redirect': "{url}#{section}".format(
 | 
					                    response = {
 | 
				
			||||||
                    url=reverse('datacenterlight:payment'),
 | 
					                        'status': False,
 | 
				
			||||||
                    section='payment_error'),
 | 
					                        'redirect': "{url}#{section}".format(
 | 
				
			||||||
                'msg_title': str(_('Error.')),
 | 
					                            url=(reverse('show_product', kwargs={
 | 
				
			||||||
                'msg_body': str(
 | 
					                                'product_slug': gp_details['product_slug']}
 | 
				
			||||||
                    _('There was a payment related error.'
 | 
					                                         ) if 'generic_payment_details' in
 | 
				
			||||||
                      ' On close of this popup, you will be redirected back to'
 | 
					                                              request.session else
 | 
				
			||||||
                      ' the payment page.'))
 | 
					                                 reverse('datacenterlight:payment')
 | 
				
			||||||
            }
 | 
					                                 ),
 | 
				
			||||||
            return JsonResponse(response)
 | 
					                            section='payment_error'),
 | 
				
			||||||
 | 
					                        'msg_title': str(_('Error.')),
 | 
				
			||||||
 | 
					                        'msg_body': str(
 | 
				
			||||||
 | 
					                            _('There was a payment related error.'
 | 
				
			||||||
 | 
					                              ' On close of this popup, you will be redirected'
 | 
				
			||||||
 | 
					                              ' back to the payment page.'))
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    return JsonResponse(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ('generic_payment_type' not in request.session or
 | 
				
			||||||
 | 
					                (request.session['generic_payment_details']['recurring'])):
 | 
				
			||||||
 | 
					            if 'generic_payment_details' in request.session:
 | 
				
			||||||
 | 
					                amount_to_be_charged = (
 | 
				
			||||||
 | 
					                    round(
 | 
				
			||||||
 | 
					                        request.session['generic_payment_details']['amount'],
 | 
				
			||||||
 | 
					                        2
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                plan_name = "generic-{0}-{1:.2f}".format(
 | 
				
			||||||
 | 
					                    request.session['generic_payment_details']['product_id'],
 | 
				
			||||||
 | 
					                    amount_to_be_charged
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                stripe_plan_id = plan_name
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                template = request.session.get('template')
 | 
				
			||||||
 | 
					                specs = request.session.get('specs')
 | 
				
			||||||
 | 
					                vm_template_id = template.get('id', 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                cpu = specs.get('cpu')
 | 
				
			||||||
 | 
					                memory = specs.get('memory')
 | 
				
			||||||
 | 
					                disk_size = specs.get('disk_size')
 | 
				
			||||||
 | 
					                amount_to_be_charged = specs.get('total_price')
 | 
				
			||||||
 | 
					                plan_name = StripeUtils.get_stripe_plan_name(
 | 
				
			||||||
 | 
					                    cpu=cpu,
 | 
				
			||||||
 | 
					                    memory=memory,
 | 
				
			||||||
 | 
					                    disk_size=disk_size,
 | 
				
			||||||
 | 
					                    price=amount_to_be_charged
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                stripe_plan_id = StripeUtils.get_stripe_plan_id(
 | 
				
			||||||
 | 
					                    cpu=cpu,
 | 
				
			||||||
 | 
					                    ram=memory,
 | 
				
			||||||
 | 
					                    ssd=disk_size,
 | 
				
			||||||
 | 
					                    version=1,
 | 
				
			||||||
 | 
					                    app='dcl',
 | 
				
			||||||
 | 
					                    price=amount_to_be_charged
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            stripe_plan = stripe_utils.get_or_create_stripe_plan(
 | 
				
			||||||
 | 
					                amount=amount_to_be_charged,
 | 
				
			||||||
 | 
					                name=plan_name,
 | 
				
			||||||
 | 
					                stripe_plan_id=stripe_plan_id)
 | 
				
			||||||
 | 
					            subscription_result = stripe_utils.subscribe_customer_to_plan(
 | 
				
			||||||
 | 
					                stripe_api_cus_id,
 | 
				
			||||||
 | 
					                [{"plan": stripe_plan.get(
 | 
				
			||||||
 | 
					                    'response_object').stripe_plan_id}])
 | 
				
			||||||
 | 
					            stripe_subscription_obj = subscription_result.get('response_object')
 | 
				
			||||||
 | 
					            # Check if the subscription was approved and is active
 | 
				
			||||||
 | 
					            if (stripe_subscription_obj is None
 | 
				
			||||||
 | 
					                    or stripe_subscription_obj.status != 'active'):
 | 
				
			||||||
 | 
					                # At this point, we have created a Stripe API card and
 | 
				
			||||||
 | 
					                # associated it with the customer; but the transaction failed
 | 
				
			||||||
 | 
					                # due to some reason. So, we would want to dissociate this card
 | 
				
			||||||
 | 
					                # here.
 | 
				
			||||||
 | 
					                # ...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                msg = subscription_result.get('error')
 | 
				
			||||||
 | 
					                messages.add_message(self.request, messages.ERROR, msg,
 | 
				
			||||||
 | 
					                                     extra_tags='failed_payment')
 | 
				
			||||||
 | 
					                response = {
 | 
				
			||||||
 | 
					                    'status': False,
 | 
				
			||||||
 | 
					                    'redirect': "{url}#{section}".format(
 | 
				
			||||||
 | 
					                        url=(reverse(
 | 
				
			||||||
 | 
					                            'show_product',
 | 
				
			||||||
 | 
					                            kwargs={'product_slug':
 | 
				
			||||||
 | 
					                                    request.session['generic_payment_details']
 | 
				
			||||||
 | 
					                                    ['product_slug']}
 | 
				
			||||||
 | 
					                        ) if 'generic_payment_details' in request.session else
 | 
				
			||||||
 | 
					                                reverse('datacenterlight:payment')
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                        section='payment_error'
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    'msg_title': str(_('Error.')),
 | 
				
			||||||
 | 
					                    'msg_body': str(
 | 
				
			||||||
 | 
					                        _('There was a payment related error.'
 | 
				
			||||||
 | 
					                          ' On close of this popup, you will be redirected back to'
 | 
				
			||||||
 | 
					                          ' the payment page.'))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return JsonResponse(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Create user if the user is not logged in and if he is not already
 | 
					        # Create user if the user is not logged in and if he is not already
 | 
				
			||||||
        # registered
 | 
					        # registered
 | 
				
			||||||
| 
						 | 
					@ -618,6 +866,118 @@ class OrderConfirmationView(DetailView):
 | 
				
			||||||
            'user': custom_user.id
 | 
					            'user': custom_user.id
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 'generic_payment_type' in request.session:
 | 
				
			||||||
 | 
					            stripe_cus = StripeCustomer.objects.filter(
 | 
				
			||||||
 | 
					                stripe_id=stripe_api_cus_id
 | 
				
			||||||
 | 
					            ).first()
 | 
				
			||||||
 | 
					            billing_address = BillingAddress(
 | 
				
			||||||
 | 
					                cardholder_name=billing_address_data['cardholder_name'],
 | 
				
			||||||
 | 
					                street_address=billing_address_data['street_address'],
 | 
				
			||||||
 | 
					                city=billing_address_data['city'],
 | 
				
			||||||
 | 
					                postal_code=billing_address_data['postal_code'],
 | 
				
			||||||
 | 
					                country=billing_address_data['country']
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            billing_address.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            order = HostingOrder.create(
 | 
				
			||||||
 | 
					                price=self.request
 | 
				
			||||||
 | 
					                          .session['generic_payment_details']['amount'],
 | 
				
			||||||
 | 
					                customer=stripe_cus,
 | 
				
			||||||
 | 
					                billing_address=billing_address,
 | 
				
			||||||
 | 
					                vm_pricing=VMPricing.get_default_pricing()
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Create a Hosting Bill
 | 
				
			||||||
 | 
					            HostingBill.create(customer=stripe_cus,
 | 
				
			||||||
 | 
					                               billing_address=billing_address)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Create Billing Address for User if he does not have one
 | 
				
			||||||
 | 
					            if not stripe_cus.user.billing_addresses.count():
 | 
				
			||||||
 | 
					                billing_address_data.update({
 | 
				
			||||||
 | 
					                    'user': stripe_cus.user.id
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                billing_address_user_form = UserBillingAddressForm(
 | 
				
			||||||
 | 
					                    billing_address_data
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                billing_address_user_form.is_valid()
 | 
				
			||||||
 | 
					                billing_address_user_form.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self.request.session['generic_payment_details']['recurring']:
 | 
				
			||||||
 | 
					                # Associate the given stripe subscription with the order
 | 
				
			||||||
 | 
					                order.set_subscription_id(
 | 
				
			||||||
 | 
					                    stripe_subscription_obj.id, card_details_dict
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                # Associate the given stripe charge id with the order
 | 
				
			||||||
 | 
					                order.set_stripe_charge(stripe_onetime_charge)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Set order status approved
 | 
				
			||||||
 | 
					            order.set_approved()
 | 
				
			||||||
 | 
					            order.generic_payment_description = gp_details["description"]
 | 
				
			||||||
 | 
					            order.generic_product_id = gp_details["product_id"]
 | 
				
			||||||
 | 
					            order.save()
 | 
				
			||||||
 | 
					            # send emails
 | 
				
			||||||
 | 
					            context = {
 | 
				
			||||||
 | 
					                'name': user.get('name'),
 | 
				
			||||||
 | 
					                'email': user.get('email'),
 | 
				
			||||||
 | 
					                'amount': gp_details['amount'],
 | 
				
			||||||
 | 
					                'description': gp_details['description'],
 | 
				
			||||||
 | 
					                'recurring': gp_details['recurring'],
 | 
				
			||||||
 | 
					                'product_name': gp_details['product_name'],
 | 
				
			||||||
 | 
					                'product_id': gp_details['product_id'],
 | 
				
			||||||
 | 
					                'order_id': order.id
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            email_data = {
 | 
				
			||||||
 | 
					                'subject': (settings.DCL_TEXT +
 | 
				
			||||||
 | 
					                            " Payment received from %s" % context['email']),
 | 
				
			||||||
 | 
					                'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
 | 
				
			||||||
 | 
					                'to': ['info@ungleich.ch'],
 | 
				
			||||||
 | 
					                'body': "\n".join(
 | 
				
			||||||
 | 
					                    ["%s=%s" % (k, v) for (k, v) in context.items()]),
 | 
				
			||||||
 | 
					                'reply_to': [context['email']],
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            send_plain_email_task.delay(email_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            email_data = {
 | 
				
			||||||
 | 
					                'subject': _("Confirmation of your payment"),
 | 
				
			||||||
 | 
					                'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
 | 
				
			||||||
 | 
					                'to': [user.get('email')],
 | 
				
			||||||
 | 
					                'body': _("Hi {name},\n\n"
 | 
				
			||||||
 | 
					                          "thank you for your order!\n"
 | 
				
			||||||
 | 
					                          "We have just received a payment of CHF {amount:.2f}"
 | 
				
			||||||
 | 
					                          " from you.{recurring}\n\n"
 | 
				
			||||||
 | 
					                          "Cheers,\nYour Data Center Light team".format(
 | 
				
			||||||
 | 
					                                 name=user.get('name'),
 | 
				
			||||||
 | 
					                                 amount=gp_details['amount'],
 | 
				
			||||||
 | 
					                                 recurring=(
 | 
				
			||||||
 | 
					                                     _(' This is a monthly recurring plan.')
 | 
				
			||||||
 | 
					                                     if gp_details['recurring'] else ''
 | 
				
			||||||
 | 
					                                 )
 | 
				
			||||||
 | 
					                             )
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                'reply_to': ['info@ungleich.ch'],
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            send_plain_email_task.delay(email_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            response = {
 | 
				
			||||||
 | 
					                'status': True,
 | 
				
			||||||
 | 
					                'redirect': (
 | 
				
			||||||
 | 
					                    reverse('hosting:orders')
 | 
				
			||||||
 | 
					                    if request.user.is_authenticated()
 | 
				
			||||||
 | 
					                    else reverse('datacenterlight:index')
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                'msg_title': str(_('Thank you for the payment.')),
 | 
				
			||||||
 | 
					                'msg_body': str(
 | 
				
			||||||
 | 
					                    _('You will soon receive a confirmation email of the '
 | 
				
			||||||
 | 
					                      'payment. You can always contact us at '
 | 
				
			||||||
 | 
					                      'info@ungleich.ch for any question that you may have.')
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            clear_all_session_vars(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return JsonResponse(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        user = {
 | 
					        user = {
 | 
				
			||||||
            'name': custom_user.name,
 | 
					            'name': custom_user.name,
 | 
				
			||||||
            'email': custom_user.email,
 | 
					            'email': custom_user.email,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ from django.conf import settings
 | 
				
			||||||
from hosting.views import (
 | 
					from hosting.views import (
 | 
				
			||||||
    RailsHostingView, DjangoHostingView, NodeJSHostingView
 | 
					    RailsHostingView, DjangoHostingView, NodeJSHostingView
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					from datacenterlight.views import PaymentOrderView
 | 
				
			||||||
from membership import urls as membership_urls
 | 
					from membership import urls as membership_urls
 | 
				
			||||||
from ungleich_page.views import LandingView
 | 
					from ungleich_page.views import LandingView
 | 
				
			||||||
from django.views.generic import RedirectView
 | 
					from django.views.generic import RedirectView
 | 
				
			||||||
| 
						 | 
					@ -29,6 +30,9 @@ urlpatterns = [
 | 
				
			||||||
    url(r'^nosystemd/', include('nosystemd.urls', namespace="nosystemd")),
 | 
					    url(r'^nosystemd/', include('nosystemd.urls', namespace="nosystemd")),
 | 
				
			||||||
    url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')),
 | 
					    url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')),
 | 
				
			||||||
    url(r'^jsi18n/(?P<packages>\S+?)/$', i18n.javascript_catalog),
 | 
					    url(r'^jsi18n/(?P<packages>\S+?)/$', i18n.javascript_catalog),
 | 
				
			||||||
 | 
					    url(r'^product/(?P<product_slug>[\w-]+)/$',
 | 
				
			||||||
 | 
					        PaymentOrderView.as_view(),
 | 
				
			||||||
 | 
					        name='show_product'),
 | 
				
			||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 | 
					] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
urlpatterns += i18n_patterns(
 | 
					urlpatterns += i18n_patterns(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,8 @@
 | 
				
			||||||
from django.contrib import admin
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import HostingOrder, HostingBill, HostingPlan
 | 
					from .models import HostingOrder, HostingBill, HostingPlan, GenericProduct
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
admin.site.register(HostingOrder)
 | 
					admin.site.register(HostingOrder)
 | 
				
			||||||
admin.site.register(HostingBill)
 | 
					admin.site.register(HostingBill)
 | 
				
			||||||
admin.site.register(HostingPlan)
 | 
					admin.site.register(HostingPlan)
 | 
				
			||||||
 | 
					admin.site.register(GenericProduct)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from membership.models import CustomUser
 | 
					from membership.models import CustomUser
 | 
				
			||||||
from utils.hosting_utils import get_all_public_keys
 | 
					from utils.hosting_utils import get_all_public_keys
 | 
				
			||||||
from .models import UserHostingKey
 | 
					from .models import UserHostingKey, GenericProduct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger(__name__)
 | 
					logger = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,6 +52,93 @@ class HostingUserLoginForm(forms.Form):
 | 
				
			||||||
            raise forms.ValidationError(_("User does not exist"))
 | 
					            raise forms.ValidationError(_("User does not exist"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProductModelChoiceField(forms.ModelChoiceField):
 | 
				
			||||||
 | 
					    def label_from_instance(self, obj):
 | 
				
			||||||
 | 
					        return obj.product_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GenericPaymentForm(forms.Form):
 | 
				
			||||||
 | 
					    product_name = ProductModelChoiceField(
 | 
				
			||||||
 | 
					        queryset=GenericProduct.objects.all().order_by('product_name'),
 | 
				
			||||||
 | 
					        empty_label=_("Choose a product"),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    amount = forms.FloatField(
 | 
				
			||||||
 | 
					        widget=forms.TextInput(
 | 
				
			||||||
 | 
					            attrs={'placeholder': _('Amount in CHF'),
 | 
				
			||||||
 | 
					                   'readonly': 'readonly', }
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        max_value=999999,
 | 
				
			||||||
 | 
					        min_value=1,
 | 
				
			||||||
 | 
					        label=_('Amount in CHF')
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    recurring = forms.BooleanField(required=False,
 | 
				
			||||||
 | 
					                                   label=_("Recurring monthly"), )
 | 
				
			||||||
 | 
					    description = forms.CharField(
 | 
				
			||||||
 | 
					        widget=forms.Textarea(attrs={'style': "height: 60px;"}),
 | 
				
			||||||
 | 
					        required=False
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = GenericProduct
 | 
				
			||||||
 | 
					        fields = ['product_name', 'amount', 'recurring', 'description']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clean_amount(self):
 | 
				
			||||||
 | 
					        amount = self.cleaned_data.get('amount')
 | 
				
			||||||
 | 
					        if (float(self.cleaned_data.get('product_name').get_actual_price()) !=
 | 
				
			||||||
 | 
					                amount):
 | 
				
			||||||
 | 
					            raise forms.ValidationError(_("Amount field does not match"))
 | 
				
			||||||
 | 
					        return amount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clean_recurring(self):
 | 
				
			||||||
 | 
					        recurring = self.cleaned_data.get('recurring')
 | 
				
			||||||
 | 
					        if (self.cleaned_data.get('product_name').product_is_subscription !=
 | 
				
			||||||
 | 
					                (True if recurring else False)):
 | 
				
			||||||
 | 
					            raise forms.ValidationError(_("Recurring field does not match"))
 | 
				
			||||||
 | 
					        return recurring
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ProductPaymentForm(GenericPaymentForm):
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        product_id = kwargs.pop('product_id', None)
 | 
				
			||||||
 | 
					        if product_id is not None:
 | 
				
			||||||
 | 
					            self.product = GenericProduct.objects.get(id=product_id)
 | 
				
			||||||
 | 
					        super(ProductPaymentForm, self).__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					        self.fields['product_name'] = forms.CharField(
 | 
				
			||||||
 | 
					            widget=forms.TextInput(
 | 
				
			||||||
 | 
					                attrs={'placeholder': _('Product name'),
 | 
				
			||||||
 | 
					                       'readonly': 'readonly'}
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        if self.product.product_is_subscription:
 | 
				
			||||||
 | 
					            self.fields['amount'].label = "{amt} ({payment_type})".format(
 | 
				
			||||||
 | 
					                amt=_('Amount in CHF'),
 | 
				
			||||||
 | 
					                payment_type=_('Monthly subscription')
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.fields['amount'].label = "{amt} ({payment_type})".format(
 | 
				
			||||||
 | 
					                amt=_('Amount in CHF'),
 | 
				
			||||||
 | 
					                payment_type=_('One time payment')
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        self.fields['recurring'].widget = forms.HiddenInput()
 | 
				
			||||||
 | 
					        self.fields['product_name'].widget.attrs['class'] = 'input-no-border'
 | 
				
			||||||
 | 
					        self.fields['amount'].widget.attrs['class'] = 'input-no-border'
 | 
				
			||||||
 | 
					        self.fields['description'].widget.attrs['class'] = 'input-no-border'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clean_amount(self):
 | 
				
			||||||
 | 
					        amount = self.cleaned_data.get('amount')
 | 
				
			||||||
 | 
					        if (self.product is None or
 | 
				
			||||||
 | 
					                float(self.product.get_actual_price()) != amount):
 | 
				
			||||||
 | 
					            raise forms.ValidationError(_("Amount field does not match"))
 | 
				
			||||||
 | 
					        return amount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clean_recurring(self):
 | 
				
			||||||
 | 
					        recurring = self.cleaned_data.get('recurring')
 | 
				
			||||||
 | 
					        if (self.product.product_is_subscription !=
 | 
				
			||||||
 | 
					                (True if recurring else False)):
 | 
				
			||||||
 | 
					            raise forms.ValidationError(_("Recurring field does not match"))
 | 
				
			||||||
 | 
					        return recurring
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class HostingUserSignupForm(forms.ModelForm):
 | 
					class HostingUserSignupForm(forms.ModelForm):
 | 
				
			||||||
    confirm_password = forms.CharField(label=_("Confirm Password"),
 | 
					    confirm_password = forms.CharField(label=_("Confirm Password"),
 | 
				
			||||||
                                       widget=forms.PasswordInput())
 | 
					                                       widget=forms.PasswordInput())
 | 
				
			||||||
| 
						 | 
					@ -111,7 +198,7 @@ class UserHostingKeyForm(forms.ModelForm):
 | 
				
			||||||
                public_key=openssh_pubkey_str).first().name
 | 
					                public_key=openssh_pubkey_str).first().name
 | 
				
			||||||
            KEY_EXISTS_MESSAGE = _(
 | 
					            KEY_EXISTS_MESSAGE = _(
 | 
				
			||||||
                "This key exists already with the name \"%(name)s\"") % {
 | 
					                "This key exists already with the name \"%(name)s\"") % {
 | 
				
			||||||
                'name': key_name}
 | 
					                                     'name': key_name}
 | 
				
			||||||
            raise forms.ValidationError(KEY_EXISTS_MESSAGE)
 | 
					            raise forms.ValidationError(KEY_EXISTS_MESSAGE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with tempfile.NamedTemporaryFile(delete=True) as tmp_public_key_file:
 | 
					        with tempfile.NamedTemporaryFile(delete=True) as tmp_public_key_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-07-05 23:15+0000\n"
 | 
					"POT-Creation-Date: 2018-09-08 08:45+0000\n"
 | 
				
			||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 | 
					"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 | 
				
			||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
					"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
				
			||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
					"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
				
			||||||
| 
						 | 
					@ -209,7 +209,7 @@ msgstr "Du hast eine neue virtuelle Maschine bestellt!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid "Your order of <strong>%(vm_name)s</strong> has been charged."
 | 
					msgid "Your order of <strong>%(vm_name)s</strong> has been charged."
 | 
				
			||||||
msgstr "Deine Bestellung von <strong>%(vm_name)s</strong> wurde erhoben."
 | 
					msgstr "Deine Bestellung von <strong>%(vm_name)s</strong> wurde entgegengenommen."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "You can view your VM detail by clicking the button below."
 | 
					msgid "You can view your VM detail by clicking the button below."
 | 
				
			||||||
msgstr "Um die Rechnung zu sehen, klicke auf den Button unten."
 | 
					msgstr "Um die Rechnung zu sehen, klicke auf den Button unten."
 | 
				
			||||||
| 
						 | 
					@ -222,7 +222,7 @@ msgstr "Dein Data Center Light Team"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid "Your order of %(vm_name)s has been charged."
 | 
					msgid "Your order of %(vm_name)s has been charged."
 | 
				
			||||||
msgstr "Deine Bestellung von %(vm_name)s wurde erhoben."
 | 
					msgstr "Deine Bestellung von %(vm_name)s wurde entgegengenommen."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "You can view your VM detail by following the link below."
 | 
					msgid "You can view your VM detail by following the link below."
 | 
				
			||||||
msgstr "Um die Rechnung zu sehen, klicke auf den Link unten."
 | 
					msgstr "Um die Rechnung zu sehen, klicke auf den Link unten."
 | 
				
			||||||
| 
						 | 
					@ -249,7 +249,7 @@ msgstr "VM Kündigung"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid ""
 | 
					msgid ""
 | 
				
			||||||
"You are receiving this email because your virutal machine <strong>"
 | 
					"You are receiving this email because your virtual machine <strong>"
 | 
				
			||||||
"%(vm_name)s</strong> has been cancelled."
 | 
					"%(vm_name)s</strong> has been cancelled."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
"Du erhälst diese E-Mail, da deine virtuelle Maschine <strong>%(vm_name)s</"
 | 
					"Du erhälst diese E-Mail, da deine virtuelle Maschine <strong>%(vm_name)s</"
 | 
				
			||||||
| 
						 | 
					@ -265,7 +265,7 @@ msgstr "NEUE VM"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid ""
 | 
					msgid ""
 | 
				
			||||||
"You are receiving this email because your virutal machine %(vm_name)s has "
 | 
					"You are receiving this email because your virtual machine %(vm_name)s has "
 | 
				
			||||||
"been cancelled."
 | 
					"been cancelled."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
"Du erhälst diese E-Mail, da deine virtuelle Maschine %(vm_name)s gekündigt "
 | 
					"Du erhälst diese E-Mail, da deine virtuelle Maschine %(vm_name)s gekündigt "
 | 
				
			||||||
| 
						 | 
					@ -290,9 +290,8 @@ msgid ""
 | 
				
			||||||
"You are not making any payment yet. After placing your order, you will be "
 | 
					"You are not making any payment yet. After placing your order, you will be "
 | 
				
			||||||
"taken to the Submit Payment Page."
 | 
					"taken to the Submit Payment Page."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
"Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst "
 | 
					"Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst, "
 | 
				
			||||||
"ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt "
 | 
					"nachdem Du die Bestellung auf der nächsten Seite bestätigt hast."
 | 
				
			||||||
"hast."
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "SUBMIT"
 | 
					msgid "SUBMIT"
 | 
				
			||||||
msgstr "ABSENDEN"
 | 
					msgstr "ABSENDEN"
 | 
				
			||||||
| 
						 | 
					@ -469,9 +468,9 @@ msgid ""
 | 
				
			||||||
"database."
 | 
					"database."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
"Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine "
 | 
					"Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine "
 | 
				
			||||||
"Kreditkartendetails unten an. Die Bezahlung wird über "
 | 
					"Kreditkartendetails unten an. Die Bezahlung wird über <a href=\"https://"
 | 
				
			||||||
"<a href=\"https://stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. "
 | 
					"stripe.com\" target=\"_blank\">Stripe</a> abgewickelt. Wir speichern Deine "
 | 
				
			||||||
"Wir speichern Deine Kreditkartendetails nicht in unserer Datenbank."
 | 
					"Kreditkartendetails nicht in unserer Datenbank."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid ""
 | 
					msgid ""
 | 
				
			||||||
"Please fill in your credit card information below. We are using <a href="
 | 
					"Please fill in your credit card information below. We are using <a href="
 | 
				
			||||||
| 
						 | 
					@ -631,6 +630,12 @@ msgstr ""
 | 
				
			||||||
"Bitte entschuldige, es scheint ein unerwarteter Fehler aufgetreten zu sein. "
 | 
					"Bitte entschuldige, es scheint ein unerwarteter Fehler aufgetreten zu sein. "
 | 
				
			||||||
"Versuche es doch bitte noch einmal."
 | 
					"Versuche es doch bitte noch einmal."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Attention:"
 | 
				
			||||||
 | 
					msgstr "Achtung:"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "terminating VM can not be reverted."
 | 
				
			||||||
 | 
					msgstr "Das Beenden kann nicht rückgängig gemacht werden."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Something doesn't work?"
 | 
					msgid "Something doesn't work?"
 | 
				
			||||||
msgstr "Etwas funktioniert nicht?"
 | 
					msgstr "Etwas funktioniert nicht?"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -643,8 +648,12 @@ msgstr "KONTAKT"
 | 
				
			||||||
msgid "Terminate your Virtual Machine"
 | 
					msgid "Terminate your Virtual Machine"
 | 
				
			||||||
msgstr "Deine Virtuelle Maschine beenden"
 | 
					msgstr "Deine Virtuelle Maschine beenden"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Do you want to cancel your Virtual Machine"
 | 
					msgid ""
 | 
				
			||||||
msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst"
 | 
					"Terminated VMs can not be revived and will not be refunded. Do you want to "
 | 
				
			||||||
 | 
					"terminate your VM?"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					"Beendete VMs können nicht wiederhergestellt oder erstattet werden. Möchtest "
 | 
				
			||||||
 | 
					"du die VM beenden?"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid ""
 | 
					msgid ""
 | 
				
			||||||
| 
						 | 
					@ -723,8 +732,8 @@ msgstr "Es scheint, als hättest du diese Karte bereits hinzugefügt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#, python-brace-format
 | 
					#, python-brace-format
 | 
				
			||||||
msgid "An error occurred while associating the card. Details: {details}"
 | 
					msgid "An error occurred while associating the card. Details: {details}"
 | 
				
			||||||
msgstr "Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: "
 | 
					msgstr ""
 | 
				
			||||||
"{details}"
 | 
					"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Successfully associated the card with your account"
 | 
					msgid "Successfully associated the card with your account"
 | 
				
			||||||
msgstr "Die Karte wurde erfolgreich mit deinem Konto verbunden"
 | 
					msgstr "Die Karte wurde erfolgreich mit deinem Konto verbunden"
 | 
				
			||||||
| 
						 | 
					@ -798,6 +807,11 @@ msgstr ""
 | 
				
			||||||
msgid "Error terminating VM"
 | 
					msgid "Error terminating VM"
 | 
				
			||||||
msgstr "Fehler beenden VM"
 | 
					msgstr "Fehler beenden VM"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					"VM terminate action timed out. Please contact support@datacenterlight.ch for "
 | 
				
			||||||
 | 
					"further information."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#, python-format
 | 
					#, python-format
 | 
				
			||||||
msgid "Virtual Machine %(vm_name)s Cancelled"
 | 
					msgid "Virtual Machine %(vm_name)s Cancelled"
 | 
				
			||||||
msgstr "Virtuelle Maschine %(vm_name)s Kündigung"
 | 
					msgstr "Virtuelle Maschine %(vm_name)s Kündigung"
 | 
				
			||||||
| 
						 | 
					@ -807,6 +821,9 @@ msgstr ""
 | 
				
			||||||
"Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es "
 | 
					"Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es "
 | 
				
			||||||
"noch einmal."
 | 
					"noch einmal."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#~ msgid "Do you want to cancel your Virtual Machine"
 | 
				
			||||||
 | 
					#~ msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#~ msgid "Reset your password"
 | 
					#~ msgid "Reset your password"
 | 
				
			||||||
#~ msgstr "Passwort zurücksetzen"
 | 
					#~ msgstr "Passwort zurücksetzen"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										41
									
								
								hosting/migrations/0048_auto_20181003_0757.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								hosting/migrations/0048_auto_20181003_0757.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					# Generated by Django 1.9.4 on 2018-10-03 07:57
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					import django.db.models.deletion
 | 
				
			||||||
 | 
					import utils.mixins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('hosting', '0047_auto_20180821_1240'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.CreateModel(
 | 
				
			||||||
 | 
					            name='GenericProduct',
 | 
				
			||||||
 | 
					            fields=[
 | 
				
			||||||
 | 
					                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
				
			||||||
 | 
					                ('product_name', models.CharField(default='', max_length=128)),
 | 
				
			||||||
 | 
					                ('product_slug', models.SlugField(help_text='An optional html id for the Section. Required to set as target of a link on page', unique=True)),
 | 
				
			||||||
 | 
					                ('product_description', models.CharField(default='', max_length=500)),
 | 
				
			||||||
 | 
					                ('created_at', models.DateTimeField(auto_now_add=True)),
 | 
				
			||||||
 | 
					                ('product_price', models.DecimalField(decimal_places=2, max_digits=6)),
 | 
				
			||||||
 | 
					                ('product_vat', models.DecimalField(decimal_places=4, default=0, max_digits=6)),
 | 
				
			||||||
 | 
					                ('product_is_subscription', models.BooleanField(default=True)),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            bases=(utils.mixins.AssignPermissionsMixin, models.Model),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='hostingorder',
 | 
				
			||||||
 | 
					            name='generic_payment_description',
 | 
				
			||||||
 | 
					            field=models.CharField(max_length=500, null=True),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='hostingorder',
 | 
				
			||||||
 | 
					            name='generic_product',
 | 
				
			||||||
 | 
					            field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.GenericProduct'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										20
									
								
								hosting/migrations/0049_auto_20181005_0736.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								hosting/migrations/0049_auto_20181005_0736.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					# Generated by Django 1.9.4 on 2018-10-05 07:36
 | 
				
			||||||
 | 
					from __future__ import unicode_literals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('hosting', '0048_auto_20181003_0757'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AlterField(
 | 
				
			||||||
 | 
					            model_name='genericproduct',
 | 
				
			||||||
 | 
					            name='product_slug',
 | 
				
			||||||
 | 
					            field=models.SlugField(help_text='An mandatory unique slug for the product', unique=True),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					@ -9,8 +9,8 @@ from django.utils.functional import cached_property
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from datacenterlight.models import VMPricing, VMTemplate
 | 
					from datacenterlight.models import VMPricing, VMTemplate
 | 
				
			||||||
from membership.models import StripeCustomer, CustomUser
 | 
					from membership.models import StripeCustomer, CustomUser
 | 
				
			||||||
from utils.models import BillingAddress
 | 
					 | 
				
			||||||
from utils.mixins import AssignPermissionsMixin
 | 
					from utils.mixins import AssignPermissionsMixin
 | 
				
			||||||
 | 
					from utils.models import BillingAddress
 | 
				
			||||||
from utils.stripe_utils import StripeUtils
 | 
					from utils.stripe_utils import StripeUtils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger(__name__)
 | 
					logger = logging.getLogger(__name__)
 | 
				
			||||||
| 
						 | 
					@ -61,6 +61,30 @@ class OrderDetail(AssignPermissionsMixin, models.Model):
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GenericProduct(AssignPermissionsMixin, models.Model):
 | 
				
			||||||
 | 
					    permissions = ('view_genericproduct',)
 | 
				
			||||||
 | 
					    product_name = models.CharField(max_length=128, default="")
 | 
				
			||||||
 | 
					    product_slug = models.SlugField(
 | 
				
			||||||
 | 
					        unique=True,
 | 
				
			||||||
 | 
					        help_text=(
 | 
				
			||||||
 | 
					            'An mandatory unique slug for the product'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    product_description = models.CharField(max_length=500, default="")
 | 
				
			||||||
 | 
					    created_at = models.DateTimeField(auto_now_add=True)
 | 
				
			||||||
 | 
					    product_price = models.DecimalField(max_digits=6, decimal_places=2)
 | 
				
			||||||
 | 
					    product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0)
 | 
				
			||||||
 | 
					    product_is_subscription = models.BooleanField(default=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return self.product_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_actual_price(self):
 | 
				
			||||||
 | 
					        return round(
 | 
				
			||||||
 | 
					            self.product_price + (self.product_price * self.product_vat), 2
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class HostingOrder(AssignPermissionsMixin, models.Model):
 | 
					class HostingOrder(AssignPermissionsMixin, models.Model):
 | 
				
			||||||
    ORDER_APPROVED_STATUS = 'Approved'
 | 
					    ORDER_APPROVED_STATUS = 'Approved'
 | 
				
			||||||
    ORDER_DECLINED_STATUS = 'Declined'
 | 
					    ORDER_DECLINED_STATUS = 'Declined'
 | 
				
			||||||
| 
						 | 
					@ -80,7 +104,13 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
 | 
				
			||||||
        OrderDetail, null=True, blank=True, default=None,
 | 
					        OrderDetail, null=True, blank=True, default=None,
 | 
				
			||||||
        on_delete=models.SET_NULL
 | 
					        on_delete=models.SET_NULL
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    generic_product = models.ForeignKey(
 | 
				
			||||||
 | 
					        GenericProduct, null=True, blank=True, default=None,
 | 
				
			||||||
 | 
					        on_delete=models.SET_NULL
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    generic_payment_description = models.CharField(
 | 
				
			||||||
 | 
					        max_length=500, null=True
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    permissions = ('view_hostingorder',)
 | 
					    permissions = ('view_hostingorder',)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
| 
						 | 
					@ -89,11 +119,18 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return ("Order Nr: #{} - VM_ID: {} - {} - {} - "
 | 
					        hosting_order_str = ("Order Nr: #{} - VM_ID: {} - {} - {} - "
 | 
				
			||||||
                "Specs: {} - Price: {}").format(
 | 
					                             "Specs: {} - Price: {}").format(
 | 
				
			||||||
            self.id, self.vm_id, self.customer.user.email, self.created_at,
 | 
					            self.id, self.vm_id, self.customer.user.email, self.created_at,
 | 
				
			||||||
            self.order_detail, self.price
 | 
					            self.order_detail, self.price
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        if self.generic_product_id is not None:
 | 
				
			||||||
 | 
					            hosting_order_str += " - Generic Payment"
 | 
				
			||||||
 | 
					            if self.stripe_charge_id is not None:
 | 
				
			||||||
 | 
					                hosting_order_str += " - One time charge"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                hosting_order_str += " - Recurring"
 | 
				
			||||||
 | 
					        return hosting_order_str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @cached_property
 | 
					    @cached_property
 | 
				
			||||||
    def status(self):
 | 
					    def status(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -146,9 +146,13 @@
 | 
				
			||||||
  text-align: center;
 | 
					  text-align: center;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vm-vmid-with-warning {
 | 
				
			||||||
 | 
					  padding: 50px 0 33px !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.vm-vmid .alert {
 | 
					.vm-vmid .alert {
 | 
				
			||||||
  margin-top: 15px;
 | 
					  margin-top: 15px;
 | 
				
			||||||
  margin-bottom: -60px;
 | 
					  margin-bottom: -25px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.vm-item-lg {
 | 
					.vm-item-lg {
 | 
				
			||||||
| 
						 | 
					@ -183,6 +187,13 @@
 | 
				
			||||||
  margin-top: 25px;
 | 
					  margin-top: 25px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.vm-terminate-warning {
 | 
				
			||||||
 | 
					  letter-spacing: 0.6px;
 | 
				
			||||||
 | 
					  font-size: 12px;
 | 
				
			||||||
 | 
					  font-weight: 400;
 | 
				
			||||||
 | 
					  color: #373636;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.vm-contact-us {
 | 
					.vm-contact-us {
 | 
				
			||||||
  margin: 25px 0 30px;
 | 
					  margin: 25px 0 30px;
 | 
				
			||||||
  /* text-align: center; */
 | 
					  /* text-align: center; */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -157,6 +157,10 @@ $( document ).ready(function() {
 | 
				
			||||||
    /* ---------------------------------------------
 | 
					    /* ---------------------------------------------
 | 
				
			||||||
     Scripts initialization
 | 
					     Scripts initialization
 | 
				
			||||||
     --------------------------------------------- */
 | 
					     --------------------------------------------- */
 | 
				
			||||||
 | 
					    var minRam = 1;
 | 
				
			||||||
 | 
					    if(window.minRam){
 | 
				
			||||||
 | 
					        minRam = window.minRam;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    var cardPricing = {
 | 
					    var cardPricing = {
 | 
				
			||||||
        'cpu': {
 | 
					        'cpu': {
 | 
				
			||||||
            'id': 'coreValue',
 | 
					            'id': 'coreValue',
 | 
				
			||||||
| 
						 | 
					@ -168,7 +172,7 @@ $( document ).ready(function() {
 | 
				
			||||||
        'ram': {
 | 
					        'ram': {
 | 
				
			||||||
            'id': 'ramValue',
 | 
					            'id': 'ramValue',
 | 
				
			||||||
            'value': 2,
 | 
					            'value': 2,
 | 
				
			||||||
            'min': 1,
 | 
					            'min': minRam,
 | 
				
			||||||
            'max': 200,
 | 
					            'max': 200,
 | 
				
			||||||
            'interval': 1
 | 
					            'interval': 1
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					@ -188,21 +192,54 @@ $( document ).ready(function() {
 | 
				
			||||||
            var data = $(this).data('minus');
 | 
					            var data = $(this).data('minus');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (cardPricing[data].value > cardPricing[data].min) {
 | 
					            if (cardPricing[data].value > cardPricing[data].min) {
 | 
				
			||||||
                cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
 | 
					                if(data === 'ram' && String(cardPricing[data].value) === "1" && minRam === 0.5){
 | 
				
			||||||
 | 
					                    cardPricing[data].value = 0.5;
 | 
				
			||||||
 | 
					                    $('#ramValue').val('0.5');
 | 
				
			||||||
 | 
					                    $("#ramValue").attr('step', 0.5);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            _fetchPricing();
 | 
					            _fetchPricing();
 | 
				
			||||||
 | 
					            $('#ramValue').data('old-value', $('#ramValue').val());
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        $('.fa-plus-circle.right').click(function(event) {
 | 
					        $('.fa-plus-circle.right').click(function(event) {
 | 
				
			||||||
            var data = $(this).data('plus');
 | 
					            var data = $(this).data('plus');
 | 
				
			||||||
            if (cardPricing[data].value < cardPricing[data].max) {
 | 
					            if (cardPricing[data].value < cardPricing[data].max) {
 | 
				
			||||||
                cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
 | 
					                if(data === 'ram' && String(cardPricing[data].value) === "0.5" && minRam === 0.5){
 | 
				
			||||||
 | 
					                    cardPricing[data].value = 1;
 | 
				
			||||||
 | 
					                    $('#ramValue').val('1');
 | 
				
			||||||
 | 
					                    $("#ramValue").attr('step', 1);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            _fetchPricing();
 | 
					            _fetchPricing();
 | 
				
			||||||
 | 
					            $('#ramValue').data('old-value', $('#ramValue').val());
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $('.input-price').change(function() {
 | 
					        $('.input-price').change(function() {
 | 
				
			||||||
            var data = $(this).attr("name");
 | 
					            var data = $(this).attr("name");
 | 
				
			||||||
            cardPricing[data].value = $('input[name=' + data + ']').val();
 | 
					            var input = $('input[name=' + data + ']');
 | 
				
			||||||
 | 
					            var inputValue = input.val();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if(data === 'ram') {
 | 
				
			||||||
 | 
					                var ramInput = $('#ramValue');
 | 
				
			||||||
 | 
					                if ($('#ramValue').data('old-value') < $('#ramValue').val()) {
 | 
				
			||||||
 | 
					                    if($('#ramValue').val() === '1' && minRam === 0.5) {
 | 
				
			||||||
 | 
					                        $("#ramValue").attr('step', 1);
 | 
				
			||||||
 | 
					                        $('#ramValue').val('1');
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    if($('#ramValue').val() === '0' && minRam === 0.5) {
 | 
				
			||||||
 | 
					                        $("#ramValue").attr('step', 0.5);
 | 
				
			||||||
 | 
					                        $('#ramValue').val('0.5');
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                inputValue = $('#ramValue').val();
 | 
				
			||||||
 | 
					                $('#ramValue').data('old-value', $('#ramValue').val());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            cardPricing[data].value = inputValue;
 | 
				
			||||||
            _fetchPricing();
 | 
					            _fetchPricing();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -236,4 +273,5 @@ $( document ).ready(function() {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _initPricing();
 | 
					    _initPricing();
 | 
				
			||||||
});
 | 
					    $('#ramValue').data('old-value', $('#ramValue').val());
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,6 +22,39 @@ function setBrandIcon(brand) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$(document).ready(function () {
 | 
					$(document).ready(function () {
 | 
				
			||||||
 | 
					    $(function () {
 | 
				
			||||||
 | 
					        $("select#id_generic_payment_form-product_name").change(function () {
 | 
				
			||||||
 | 
					            var gp_form = $('#generic-payment-form');
 | 
				
			||||||
 | 
					            $.ajax({
 | 
				
			||||||
 | 
					                url: gp_form.attr('action'),
 | 
				
			||||||
 | 
					                type: 'POST',
 | 
				
			||||||
 | 
					                data: gp_form.serialize(),
 | 
				
			||||||
 | 
					                init: function () {
 | 
				
			||||||
 | 
					                    console.log("init")
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                success: function (data) {
 | 
				
			||||||
 | 
					                    if (data.amount !== undefined) {
 | 
				
			||||||
 | 
					                        $("#id_generic_payment_form-amount").val(data.amount);
 | 
				
			||||||
 | 
					                        if (data.isSubscription) {
 | 
				
			||||||
 | 
					                            $('#id_generic_payment_form-recurring').prop('checked', true);
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            $('#id_generic_payment_form-recurring').prop('checked', false);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        $("#id_generic_payment_form-amount").val('');
 | 
				
			||||||
 | 
					                        $('#id_generic_payment_form-recurring').prop('checked', false);
 | 
				
			||||||
 | 
					                        console.log("No product found")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                error: function (xmlhttprequest, textstatus, message) {
 | 
				
			||||||
 | 
					                    $("#id_generic_payment_form-amount").val('');
 | 
				
			||||||
 | 
					                    $('#id_generic_payment_form-recurring').prop('checked', false);
 | 
				
			||||||
 | 
					                    console.log("Error fetching product")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $.ajaxSetup({
 | 
					    $.ajaxSetup({
 | 
				
			||||||
        beforeSend: function (xhr, settings) {
 | 
					        beforeSend: function (xhr, settings) {
 | 
				
			||||||
            function getCookie(name) {
 | 
					            function getCookie(name) {
 | 
				
			||||||
| 
						 | 
					@ -124,17 +157,35 @@ $(document).ready(function () {
 | 
				
			||||||
        $('#billing-form').submit();
 | 
					        $('#billing-form').submit();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function getCookie(name) {
 | 
				
			||||||
 | 
					        var value = "; " + document.cookie;
 | 
				
			||||||
 | 
					        var parts = value.split("; " + name + "=");
 | 
				
			||||||
 | 
					        if (parts.length === 2) return parts.pop().split(";").shift();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function submitBillingForm() {
 | 
				
			||||||
 | 
					        var billing_form = $('#billing-form');
 | 
				
			||||||
 | 
					        var recurring_input = $('#id_generic_payment_form-recurring');
 | 
				
			||||||
 | 
					        billing_form.append('<input type="hidden" name="generic_payment_form-product_name" value="' + $('#id_generic_payment_form-product_name').val() + '" />');
 | 
				
			||||||
 | 
					        billing_form.append('<input type="hidden" name="generic_payment_form-amount" value="' + $('#id_generic_payment_form-amount').val() + '" />');
 | 
				
			||||||
 | 
					        if (recurring_input.attr('type') === 'hidden') {
 | 
				
			||||||
 | 
					            billing_form.append('<input type="hidden" name="generic_payment_form-recurring" value="' + (recurring_input.val() === 'True' ? 'on' : '') + '" />');
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            billing_form.append('<input type="hidden" name="generic_payment_form-recurring" value="' + (recurring_input.prop('checked') ? 'on' : '') + '" />');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        billing_form.append('<input type="hidden" name="generic_payment_form-description" value="' + $('#id_generic_payment_form-description').val() + '" />');
 | 
				
			||||||
 | 
					        billing_form.submit();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var $form_new = $('#payment-form-new');
 | 
					    var $form_new = $('#payment-form-new');
 | 
				
			||||||
    $form_new.submit(payWithStripe_new);
 | 
					    $form_new.submit(payWithStripe_new);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    function payWithStripe_new(e) {
 | 
					    function payWithStripe_new(e) {
 | 
				
			||||||
        e.preventDefault();
 | 
					        e.preventDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        function stripeTokenHandler(token) {
 | 
					        function stripeTokenHandler(token) {
 | 
				
			||||||
            // Insert the token ID into the form so it gets submitted to the server
 | 
					            // Insert the token ID into the form so it gets submitted to the server
 | 
				
			||||||
            $('#id_token').val(token.id);
 | 
					            $('#id_token').val(token.id);
 | 
				
			||||||
            $('#billing-form').submit();
 | 
					            submitBillingForm();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -196,10 +247,10 @@ $(document).ready(function () {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $('.credit-card-info .btn.choice-btn').click(function(){
 | 
					    $('.credit-card-info .btn.choice-btn').click(function () {
 | 
				
			||||||
            var id = this.dataset['id_card'];
 | 
					        var id = this.dataset['id_card'];
 | 
				
			||||||
            $('#id_card').val(id);
 | 
					        $('#id_card').val(id);
 | 
				
			||||||
            $('#billing-form').submit();
 | 
					        submitBillingForm();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -134,3 +134,15 @@ $(document).ready(function() {
 | 
				
			||||||
        $(this).find('.modal-footer .btn').addClass('hide');
 | 
					        $(this).find('.modal-footer .btn').addClass('hide');
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.onload = function () {
 | 
				
			||||||
 | 
					    var locale_dates = document.getElementsByClassName("locale_date");
 | 
				
			||||||
 | 
					    var formats = ['YYYY-MM-DD hh:mm a'];
 | 
				
			||||||
 | 
					    var i;
 | 
				
			||||||
 | 
					    for (i = 0; i < locale_dates.length; i++) {
 | 
				
			||||||
 | 
					        var oldDate = moment.utc(locale_dates[i].textContent, formats);
 | 
				
			||||||
 | 
					        var outputFormat = locale_dates[i].getAttribute('data-format') || oldDate._f;
 | 
				
			||||||
 | 
					        locale_dates[i].innerHTML = oldDate.local().format(outputFormat);
 | 
				
			||||||
 | 
					        locale_dates[i].className += ' done';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -25,7 +25,7 @@
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
            <td style="padding-top: 25px; font-size: 16px;">
 | 
					            <td style="padding-top: 25px; font-size: 16px;">
 | 
				
			||||||
                <p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
 | 
					                <p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
 | 
				
			||||||
                    {% blocktrans %}You are receiving this email because your virutal machine <strong>{{ vm_name }}</strong> has been cancelled.{% endblocktrans %}
 | 
					                    {% blocktrans %}You are receiving this email because your virtual machine <strong>{{ vm_name }}</strong> has been cancelled.{% endblocktrans %}
 | 
				
			||||||
                </p>
 | 
					                </p>
 | 
				
			||||||
                <p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
 | 
					                <p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
 | 
				
			||||||
                    {% blocktrans %}You can always order a new VM by clicking the button below.{% endblocktrans %}
 | 
					                    {% blocktrans %}You can always order a new VM by clicking the button below.{% endblocktrans %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% trans "Virtual Machine Cancellation" %}
 | 
					{% trans "Virtual Machine Cancellation" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% blocktrans %}You are receiving this email because your virutal machine {{vm_name}} has been cancelled.{% endblocktrans %}
 | 
					{% blocktrans %}You are receiving this email because your virtual machine {{vm_name}} has been cancelled.{% endblocktrans %}
 | 
				
			||||||
{% blocktrans %}You can always order a new VM by following the link below.{% endblocktrans %}
 | 
					{% blocktrans %}You can always order a new VM by following the link below.{% endblocktrans %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{{ base_url }}{% url 'hosting:create_virtual_machine' %}
 | 
					{{ base_url }}{% url 'hosting:create_virtual_machine' %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,7 +39,7 @@
 | 
				
			||||||
                    {% endif %}
 | 
					                    {% endif %}
 | 
				
			||||||
                </span>
 | 
					                </span>
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
            {% if order %}
 | 
					            {% if order and vm %}
 | 
				
			||||||
                <p>
 | 
					                <p>
 | 
				
			||||||
                    <strong>{% trans "Status" %}: </strong>
 | 
					                    <strong>{% trans "Status" %}: </strong>
 | 
				
			||||||
                    <strong>
 | 
					                    <strong>
 | 
				
			||||||
| 
						 | 
					@ -93,77 +93,104 @@
 | 
				
			||||||
            <hr>
 | 
					            <hr>
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                <h4>{% trans "Order summary" %}</h4>
 | 
					                <h4>{% trans "Order summary" %}</h4>
 | 
				
			||||||
                <p>
 | 
					                {% if vm %}
 | 
				
			||||||
                    <strong>{% trans "Product" %}:</strong> 
 | 
					                    <p>
 | 
				
			||||||
                    {% if vm.name %}
 | 
					                        <strong>{% trans "Product" %}:</strong> 
 | 
				
			||||||
                        {{ vm.name }}
 | 
					                        {% if vm.name %}
 | 
				
			||||||
                    {% else %}
 | 
					                            {{ vm.name }}
 | 
				
			||||||
                        {{ request.session.template.name }}
 | 
					                        {% else %}
 | 
				
			||||||
                    {% endif %}
 | 
					                            {{ request.session.template.name }}
 | 
				
			||||||
                </p>
 | 
					 | 
				
			||||||
                <div class="row">
 | 
					 | 
				
			||||||
                    <div class="col-sm-6">
 | 
					 | 
				
			||||||
                        {% if vm.created_at %}
 | 
					 | 
				
			||||||
                            <p>
 | 
					 | 
				
			||||||
                                <span>{% trans "Period" %}: </span>
 | 
					 | 
				
			||||||
                                <span>
 | 
					 | 
				
			||||||
                                    <span class="locale_date" data-format="YYYY/MM/DD">{{ vm.created_at|date:'Y-m-d h:i a' }}</span> - <span class="locale_date" data-format="YYYY/MM/DD">{{ subscription_end_date|date:'Y-m-d h:i a' }}</span>
 | 
					 | 
				
			||||||
                                </span>
 | 
					 | 
				
			||||||
                            </p>
 | 
					 | 
				
			||||||
                        {% endif %}
 | 
					                        {% endif %}
 | 
				
			||||||
                        <p>
 | 
					                    </p>
 | 
				
			||||||
                            <span>{% trans "Cores" %}: </span>
 | 
					                    <div class="row">
 | 
				
			||||||
                            {% if vm.cores %}
 | 
					 | 
				
			||||||
                                <strong class="pull-right">{{vm.cores|floatformat}}</strong>
 | 
					 | 
				
			||||||
                            {% else %}
 | 
					 | 
				
			||||||
                                <strong class="pull-right">{{vm.cpu|floatformat}}</strong>
 | 
					 | 
				
			||||||
                            {% endif %}
 | 
					 | 
				
			||||||
                        </p>
 | 
					 | 
				
			||||||
                        <p>
 | 
					 | 
				
			||||||
                            <span>{% trans "Memory" %}: </span>
 | 
					 | 
				
			||||||
                            <strong class="pull-right">{{vm.memory}} GB</strong>
 | 
					 | 
				
			||||||
                        </p>
 | 
					 | 
				
			||||||
                        <p>
 | 
					 | 
				
			||||||
                            <span>{% trans "Disk space" %}: </span>
 | 
					 | 
				
			||||||
                            <strong class="pull-right">{{vm.disk_size}} GB</strong>
 | 
					 | 
				
			||||||
                        </p>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <div class="col-sm-12">
 | 
					 | 
				
			||||||
                        <hr class="thin-hr">
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    {% if vm.vat > 0 or vm.discount.amount > 0 %}
 | 
					 | 
				
			||||||
                        <div class="col-sm-6">
 | 
					                        <div class="col-sm-6">
 | 
				
			||||||
                            <div class="subtotal-price">
 | 
					                            {% if vm.created_at %}
 | 
				
			||||||
                                {% if vm.vat > 0 %}
 | 
					                                <p>
 | 
				
			||||||
                                    <p>
 | 
					                                    <span>{% trans "Period" %}: </span>
 | 
				
			||||||
                                        <strong>{% trans "Subtotal" %} </strong>
 | 
					                                    <span>
 | 
				
			||||||
                                        <strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
 | 
					                                        <span class="locale_date" data-format="YYYY/MM/DD">{{ vm.created_at|date:'Y-m-d h:i a' }}</span> - <span class="locale_date" data-format="YYYY/MM/DD">{{ subscription_end_date|date:'Y-m-d h:i a' }}</span>
 | 
				
			||||||
                                    </p>
 | 
					                                    </span>
 | 
				
			||||||
                                    <p>
 | 
					                                </p>
 | 
				
			||||||
                                        <small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) </small>
 | 
					                            {% endif %}
 | 
				
			||||||
                                        <strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
 | 
					                            <p>
 | 
				
			||||||
                                    </p>
 | 
					                                <span>{% trans "Cores" %}: </span>
 | 
				
			||||||
 | 
					                                {% if vm.cores %}
 | 
				
			||||||
 | 
					                                    <strong class="pull-right">{{vm.cores|floatformat}}</strong>
 | 
				
			||||||
 | 
					                                {% else %}
 | 
				
			||||||
 | 
					                                    <strong class="pull-right">{{vm.cpu|floatformat}}</strong>
 | 
				
			||||||
                                {% endif %}
 | 
					                                {% endif %}
 | 
				
			||||||
                                {% if vm.discount.amount > 0 %}
 | 
					                            </p>
 | 
				
			||||||
                                    <p class="text-primary">
 | 
					                            <p>
 | 
				
			||||||
                                        {%trans "Discount" as discount_name %}
 | 
					                                <span>{% trans "Memory" %}: </span>
 | 
				
			||||||
                                        <strong>{{ vm.discount.name|default:discount_name }} </strong>
 | 
					                                <strong class="pull-right">{{vm.memory}} GB</strong>
 | 
				
			||||||
                                        <strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
 | 
					                            </p>
 | 
				
			||||||
                                    </p>
 | 
					                            <p>
 | 
				
			||||||
                                {% endif %}
 | 
					                                <span>{% trans "Disk space" %}: </span>
 | 
				
			||||||
                            </div>
 | 
					                                <strong class="pull-right">{{vm.disk_size}} GB</strong>
 | 
				
			||||||
 | 
					                            </p>
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                        <div class="col-sm-12">
 | 
					                        <div class="col-sm-12">
 | 
				
			||||||
                            <hr class="thin-hr">
 | 
					                            <hr class="thin-hr">
 | 
				
			||||||
                        </div>
 | 
					                        </div>
 | 
				
			||||||
                    {% endif %}
 | 
					                        {% if vm.vat > 0 or vm.discount.amount > 0 %}
 | 
				
			||||||
                    <div class="col-sm-6">
 | 
					                            <div class="col-sm-6">
 | 
				
			||||||
                        <p class="total-price">
 | 
					                                <div class="subtotal-price">
 | 
				
			||||||
                            <strong>{% trans "Total" %} </strong>
 | 
					                                    {% if vm.vat > 0 %}
 | 
				
			||||||
                            <strong class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %} CHF</strong>
 | 
					                                        <p>
 | 
				
			||||||
                        </p>
 | 
					                                            <strong>{% trans "Subtotal" %} </strong>
 | 
				
			||||||
 | 
					                                            <strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
 | 
				
			||||||
 | 
					                                        </p>
 | 
				
			||||||
 | 
					                                        <p>
 | 
				
			||||||
 | 
					                                            <small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) </small>
 | 
				
			||||||
 | 
					                                            <strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
 | 
				
			||||||
 | 
					                                        </p>
 | 
				
			||||||
 | 
					                                    {% endif %}
 | 
				
			||||||
 | 
					                                    {% if vm.discount.amount > 0 %}
 | 
				
			||||||
 | 
					                                        <p class="text-primary">
 | 
				
			||||||
 | 
					                                            {%trans "Discount" as discount_name %}
 | 
				
			||||||
 | 
					                                            <strong>{{ vm.discount.name|default:discount_name }} </strong>
 | 
				
			||||||
 | 
					                                            <strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
 | 
				
			||||||
 | 
					                                        </p>
 | 
				
			||||||
 | 
					                                    {% endif %}
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <div class="col-sm-12">
 | 
				
			||||||
 | 
					                                <hr class="thin-hr">
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        {% endif %}
 | 
				
			||||||
 | 
					                        <div class="col-sm-6">
 | 
				
			||||||
 | 
					                            <p class="total-price">
 | 
				
			||||||
 | 
					                                <strong>{% trans "Total" %} </strong>
 | 
				
			||||||
 | 
					                                <strong class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %} CHF</strong>
 | 
				
			||||||
 | 
					                            </p>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                {% else %}
 | 
				
			||||||
 | 
					                    <p>
 | 
				
			||||||
 | 
					                        <strong>{% trans "Product" %}:</strong> 
 | 
				
			||||||
 | 
					                        {{ product_name }}
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                    <div class="row">
 | 
				
			||||||
 | 
					                        <div class="col-sm-6">
 | 
				
			||||||
 | 
					                            <p>
 | 
				
			||||||
 | 
					                                <span>{% trans "Amount" %}: </span>
 | 
				
			||||||
 | 
					                                <strong class="pull-right">{{order.price|floatformat:2|intcomma}} CHF</strong>
 | 
				
			||||||
 | 
					                            </p>
 | 
				
			||||||
 | 
					                            {% if order.generic_payment_description %}
 | 
				
			||||||
 | 
					                                <p>
 | 
				
			||||||
 | 
					                                    <span>{% trans "Description" %}: </span>
 | 
				
			||||||
 | 
					                                    <strong class="pull-right">{{order.generic_payment_description}}</strong>
 | 
				
			||||||
 | 
					                                </p>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                            {% if order.subscription_id %}
 | 
				
			||||||
 | 
					                            <p>
 | 
				
			||||||
 | 
					                                <span>{% trans "Recurring" %}: </span>
 | 
				
			||||||
 | 
					                                <strong class="pull-right">{{order.created_at|date:'d'|ordinal}} {% trans "of every month" %}</strong>
 | 
				
			||||||
 | 
					                            </p>
 | 
				
			||||||
 | 
					                            {% endif %}
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <hr class="thin-hr">
 | 
					            <hr class="thin-hr">
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
| 
						 | 
					@ -229,17 +256,6 @@
 | 
				
			||||||
<script type="text/javascript">
 | 
					<script type="text/javascript">
 | 
				
			||||||
    {% trans "Some problem encountered. Please try again later." as err_msg %}
 | 
					    {% trans "Some problem encountered. Please try again later." as err_msg %}
 | 
				
			||||||
    var create_vm_error_message = '{{err_msg|safe}}';
 | 
					    var create_vm_error_message = '{{err_msg|safe}}';
 | 
				
			||||||
    window.onload = function () {
 | 
					 | 
				
			||||||
        var locale_dates = document.getElementsByClassName("locale_date");
 | 
					 | 
				
			||||||
        var formats = ['YYYY-MM-DD hh:mm a']
 | 
					 | 
				
			||||||
        var i;
 | 
					 | 
				
			||||||
        for (i = 0; i < locale_dates.length; i++) {
 | 
					 | 
				
			||||||
            var oldDate = moment.utc(locale_dates[i].textContent, formats);
 | 
					 | 
				
			||||||
            var outputFormat = locale_dates[i].getAttribute('data-format') || oldDate._f;
 | 
					 | 
				
			||||||
            locale_dates[i].innerHTML = oldDate.local().format(outputFormat);
 | 
					 | 
				
			||||||
            locale_dates[i].className += ' done';
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
{%endblock%}
 | 
					{%endblock%}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,7 @@
 | 
				
			||||||
            {% for order in orders %}
 | 
					            {% for order in orders %}
 | 
				
			||||||
                <tr>
 | 
					                <tr>
 | 
				
			||||||
                    <td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td>
 | 
					                    <td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td>
 | 
				
			||||||
                    <td class="xs-td-bighalf" data-header="{% trans 'Date' %}">{{ order.created_at | date:"M d, Y H:i" }}</td>
 | 
					                    <td class="xs-td-bighalf locale_date" data-header="{% trans 'Date' %}">{{ order.created_at | date:'Y-m-d h:i a' }}</td>
 | 
				
			||||||
                    <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|floatformat:2|intcomma }}</td>
 | 
					                    <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|floatformat:2|intcomma }}</td>
 | 
				
			||||||
                    <td class="text-right last-td">
 | 
					                    <td class="text-right last-td">
 | 
				
			||||||
                        <a class="btn btn-order-detail" href="{% url 'hosting:orders' order.pk %}">{% trans 'See Invoice' %}</a>
 | 
					                        <a class="btn btn-order-detail" href="{% url 'hosting:orders' order.pk %}">{% trans 'See Invoice' %}</a>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,8 +18,8 @@
 | 
				
			||||||
                    <h3>{%trans "Billing Address" %}</h3>
 | 
					                    <h3>{%trans "Billing Address" %}</h3>
 | 
				
			||||||
                    <hr>
 | 
					                    <hr>
 | 
				
			||||||
                    <form role="form" id="billing-form" method="post" action="" novalidate>
 | 
					                    <form role="form" id="billing-form" method="post" action="" novalidate>
 | 
				
			||||||
 | 
					                        {% csrf_token %}
 | 
				
			||||||
                        {% for field in form %}
 | 
					                        {% for field in form %}
 | 
				
			||||||
                            {% csrf_token %}
 | 
					 | 
				
			||||||
                            {% bootstrap_field field show_label=False type='fields' bound_css_class='' %}
 | 
					                            {% bootstrap_field field show_label=False type='fields' bound_css_class='' %}
 | 
				
			||||||
                        {% endfor %}
 | 
					                        {% endfor %}
 | 
				
			||||||
                        <div class="form-group text-right">
 | 
					                        <div class="form-group text-right">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,7 +51,7 @@
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<div class="vm-detail-item">
 | 
								<div class="vm-detail-item">
 | 
				
			||||||
				<h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2>
 | 
									<h2 class="vm-detail-title">{% trans "Status" %} <img src="{% static 'hosting/img/connected.svg' %}" class="un-icon"></h2>
 | 
				
			||||||
				<div class="vm-vmid">
 | 
									<div class="vm-vmid vm-vmid-with-warning">
 | 
				
			||||||
					<div class="vm-item-subtitle">{% trans "Your VM is" %}</div>
 | 
										<div class="vm-item-subtitle">{% trans "Your VM is" %}</div>
 | 
				
			||||||
						<div id="terminate-VM" data-alt="{% trans 'Terminating' %}">
 | 
											<div id="terminate-VM" data-alt="{% trans 'Terminating' %}">
 | 
				
			||||||
							{% if virtual_machine.state == 'PENDING' %}
 | 
												{% if virtual_machine.state == 'PENDING' %}
 | 
				
			||||||
| 
						 | 
					@ -74,6 +74,10 @@
 | 
				
			||||||
							{% endif %}
 | 
												{% endif %}
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
 | 
									<div class="vm-terminate-warning text-center">
 | 
				
			||||||
 | 
										<p>{% trans "Attention:" %}</p>
 | 
				
			||||||
 | 
										<p>{% trans "terminating VM can not be reverted." %}</p>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div class="vm-contact-us">
 | 
							<div class="vm-contact-us">
 | 
				
			||||||
| 
						 | 
					@ -105,7 +109,7 @@
 | 
				
			||||||
					<div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div>
 | 
										<div class="modal-icon"><i class="fa fa-ban" aria-hidden="true"></i></div>
 | 
				
			||||||
					<h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine" %}</h4>
 | 
										<h4 class="modal-title" id="ModalLabel">{% trans "Terminate your Virtual Machine" %}</h4>
 | 
				
			||||||
					<div class="modal-text">
 | 
										<div class="modal-text">
 | 
				
			||||||
						<p>{% trans "Do you want to cancel your Virtual Machine" %} ?</p>
 | 
											<p>{% trans "Terminated VMs can not be revived and will not be refunded. Do you want to terminate your VM?" %}</p>
 | 
				
			||||||
						<p><strong>{{virtual_machine.name}}</strong></p>
 | 
											<p><strong>{{virtual_machine.name}}</strong></p>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
          <div class="modal-footer">
 | 
					          <div class="modal-footer">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										165
									
								
								hosting/views.py
									
										
									
									
									
								
							
							
						
						
									
										165
									
								
								hosting/views.py
									
										
									
									
									
								
							| 
						 | 
					@ -32,6 +32,7 @@ from stored_messages.api import mark_read
 | 
				
			||||||
from stored_messages.models import Message
 | 
					from stored_messages.models import Message
 | 
				
			||||||
from stored_messages.settings import stored_messages_settings
 | 
					from stored_messages.settings import stored_messages_settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from datacenterlight.cms_models import DCLCalculatorPluginModel
 | 
				
			||||||
from datacenterlight.models import VMTemplate, VMPricing
 | 
					from datacenterlight.models import VMTemplate, VMPricing
 | 
				
			||||||
from datacenterlight.utils import create_vm, get_cms_integration
 | 
					from datacenterlight.utils import create_vm, get_cms_integration
 | 
				
			||||||
from hosting.models import UserCardDetail
 | 
					from hosting.models import UserCardDetail
 | 
				
			||||||
| 
						 | 
					@ -59,7 +60,8 @@ from .forms import (
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from .mixins import ProcessVMSelectionMixin, HostingContextMixin
 | 
					from .mixins import ProcessVMSelectionMixin, HostingContextMixin
 | 
				
			||||||
from .models import (
 | 
					from .models import (
 | 
				
			||||||
    HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail
 | 
					    HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail,
 | 
				
			||||||
 | 
					    GenericProduct
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger(__name__)
 | 
					logger = logging.getLogger(__name__)
 | 
				
			||||||
| 
						 | 
					@ -862,32 +864,20 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
 | 
				
			||||||
                raise Http404
 | 
					                raise Http404
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if obj is not None:
 | 
					        if obj is not None:
 | 
				
			||||||
            # invoice for previous order
 | 
					            if obj.generic_product_id is not None:
 | 
				
			||||||
            try:
 | 
					                # generic payment case
 | 
				
			||||||
                vm_detail = VMDetail.objects.get(vm_id=obj.vm_id)
 | 
					                logger.debug("Generic payment case")
 | 
				
			||||||
                context['vm'] = vm_detail.__dict__
 | 
					                context['product_name'] = GenericProduct.objects.get(
 | 
				
			||||||
                context['vm']['name'] = '{}-{}'.format(
 | 
					                    id=obj.generic_product_id
 | 
				
			||||||
                    context['vm']['configuration'], context['vm']['vm_id'])
 | 
					                ).product_name
 | 
				
			||||||
                price, vat, vat_percent, discount = get_vm_price_with_vat(
 | 
					            else:
 | 
				
			||||||
                    cpu=context['vm']['cores'],
 | 
					                # invoice for previous order
 | 
				
			||||||
                    ssd_size=context['vm']['disk_size'],
 | 
					                logger.debug("Invoice of VM order")
 | 
				
			||||||
                    memory=context['vm']['memory'],
 | 
					 | 
				
			||||||
                    pricing_name=(obj.vm_pricing.name
 | 
					 | 
				
			||||||
                                  if obj.vm_pricing else 'default')
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                context['vm']['vat'] = vat
 | 
					 | 
				
			||||||
                context['vm']['price'] = price
 | 
					 | 
				
			||||||
                context['vm']['discount'] = discount
 | 
					 | 
				
			||||||
                context['vm']['vat_percent'] = vat_percent
 | 
					 | 
				
			||||||
                context['vm']['total_price'] = price + vat - discount['amount']
 | 
					 | 
				
			||||||
                context['subscription_end_date'] = vm_detail.end_date()
 | 
					 | 
				
			||||||
            except VMDetail.DoesNotExist:
 | 
					 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    manager = OpenNebulaManager(
 | 
					                    vm_detail = VMDetail.objects.get(vm_id=obj.vm_id)
 | 
				
			||||||
                        email=owner.email, password=owner.password
 | 
					                    context['vm'] = vm_detail.__dict__
 | 
				
			||||||
                    )
 | 
					                    context['vm']['name'] = '{}-{}'.format(
 | 
				
			||||||
                    vm = manager.get_vm(obj.vm_id)
 | 
					                        context['vm']['configuration'], context['vm']['vm_id'])
 | 
				
			||||||
                    context['vm'] = VirtualMachineSerializer(vm).data
 | 
					 | 
				
			||||||
                    price, vat, vat_percent, discount = get_vm_price_with_vat(
 | 
					                    price, vat, vat_percent, discount = get_vm_price_with_vat(
 | 
				
			||||||
                        cpu=context['vm']['cores'],
 | 
					                        cpu=context['vm']['cores'],
 | 
				
			||||||
                        ssd_size=context['vm']['disk_size'],
 | 
					                        ssd_size=context['vm']['disk_size'],
 | 
				
			||||||
| 
						 | 
					@ -899,23 +889,43 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
 | 
				
			||||||
                    context['vm']['price'] = price
 | 
					                    context['vm']['price'] = price
 | 
				
			||||||
                    context['vm']['discount'] = discount
 | 
					                    context['vm']['discount'] = discount
 | 
				
			||||||
                    context['vm']['vat_percent'] = vat_percent
 | 
					                    context['vm']['vat_percent'] = vat_percent
 | 
				
			||||||
                    context['vm']['total_price'] = (
 | 
					                    context['vm']['total_price'] = price + vat - discount['amount']
 | 
				
			||||||
                            price + vat - discount['amount']
 | 
					                    context['subscription_end_date'] = vm_detail.end_date()
 | 
				
			||||||
                    )
 | 
					                except VMDetail.DoesNotExist:
 | 
				
			||||||
                except WrongIdError:
 | 
					                    try:
 | 
				
			||||||
                    messages.error(
 | 
					                        manager = OpenNebulaManager(
 | 
				
			||||||
                        self.request,
 | 
					                            email=owner.email, password=owner.password
 | 
				
			||||||
                        _('The VM you are looking for is unavailable at the '
 | 
					                        )
 | 
				
			||||||
                          'moment. Please contact Data Center Light support.')
 | 
					                        vm = manager.get_vm(obj.vm_id)
 | 
				
			||||||
                    )
 | 
					                        context['vm'] = VirtualMachineSerializer(vm).data
 | 
				
			||||||
                    self.kwargs['error'] = 'WrongIdError'
 | 
					                        price, vat, vat_percent, discount = get_vm_price_with_vat(
 | 
				
			||||||
                    context['error'] = 'WrongIdError'
 | 
					                            cpu=context['vm']['cores'],
 | 
				
			||||||
                except ConnectionRefusedError:
 | 
					                            ssd_size=context['vm']['disk_size'],
 | 
				
			||||||
                    messages.error(
 | 
					                            memory=context['vm']['memory'],
 | 
				
			||||||
                        self.request,
 | 
					                            pricing_name=(obj.vm_pricing.name
 | 
				
			||||||
                        _('In order to create a VM, you need to create/upload '
 | 
					                                          if obj.vm_pricing else 'default')
 | 
				
			||||||
                          'your SSH KEY first.')
 | 
					                        )
 | 
				
			||||||
                    )
 | 
					                        context['vm']['vat'] = vat
 | 
				
			||||||
 | 
					                        context['vm']['price'] = price
 | 
				
			||||||
 | 
					                        context['vm']['discount'] = discount
 | 
				
			||||||
 | 
					                        context['vm']['vat_percent'] = vat_percent
 | 
				
			||||||
 | 
					                        context['vm']['total_price'] = (
 | 
				
			||||||
 | 
					                                price + vat - discount['amount']
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    except WrongIdError:
 | 
				
			||||||
 | 
					                        messages.error(
 | 
				
			||||||
 | 
					                            self.request,
 | 
				
			||||||
 | 
					                            _('The VM you are looking for is unavailable at the '
 | 
				
			||||||
 | 
					                              'moment. Please contact Data Center Light support.')
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        self.kwargs['error'] = 'WrongIdError'
 | 
				
			||||||
 | 
					                        context['error'] = 'WrongIdError'
 | 
				
			||||||
 | 
					                    except ConnectionRefusedError:
 | 
				
			||||||
 | 
					                        messages.error(
 | 
				
			||||||
 | 
					                            self.request,
 | 
				
			||||||
 | 
					                            _('In order to create a VM, you need to create/upload '
 | 
				
			||||||
 | 
					                              'your SSH KEY first.')
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # new order, confirm payment
 | 
					            # new order, confirm payment
 | 
				
			||||||
            if 'token' in self.request.session:
 | 
					            if 'token' in self.request.session:
 | 
				
			||||||
| 
						 | 
					@ -1032,14 +1042,20 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
 | 
				
			||||||
        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('total_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(
 | 
				
			||||||
                                                     memory=memory,
 | 
					            cpu=cpu,
 | 
				
			||||||
                                                     disk_size=disk_size)
 | 
					            memory=memory,
 | 
				
			||||||
        stripe_plan_id = StripeUtils.get_stripe_plan_id(cpu=cpu,
 | 
					            disk_size=disk_size,
 | 
				
			||||||
                                                        ram=memory,
 | 
					            price=amount_to_be_charged
 | 
				
			||||||
                                                        ssd=disk_size,
 | 
					        )
 | 
				
			||||||
                                                        version=1,
 | 
					        stripe_plan_id = StripeUtils.get_stripe_plan_id(
 | 
				
			||||||
                                                        app='dcl')
 | 
					            cpu=cpu,
 | 
				
			||||||
 | 
					            ram=memory,
 | 
				
			||||||
 | 
					            ssd=disk_size,
 | 
				
			||||||
 | 
					            version=1,
 | 
				
			||||||
 | 
					            app='dcl',
 | 
				
			||||||
 | 
					            price=amount_to_be_charged
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        stripe_plan = stripe_utils.get_or_create_stripe_plan(
 | 
					        stripe_plan = stripe_utils.get_or_create_stripe_plan(
 | 
				
			||||||
            amount=amount_to_be_charged,
 | 
					            amount=amount_to_be_charged,
 | 
				
			||||||
            name=plan_name,
 | 
					            name=plan_name,
 | 
				
			||||||
| 
						 | 
					@ -1183,7 +1199,29 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
 | 
				
			||||||
            raise ValidationError(_('Invalid number of cores'))
 | 
					            raise ValidationError(_('Invalid number of cores'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def validate_memory(self, value):
 | 
					    def validate_memory(self, value):
 | 
				
			||||||
        if (value > 200) or (value < 1):
 | 
					        if 'pid' in self.request.POST:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                plugin = DCLCalculatorPluginModel.objects.get(
 | 
				
			||||||
 | 
					                             id=self.request.POST['pid']
 | 
				
			||||||
 | 
					                         )
 | 
				
			||||||
 | 
					            except DCLCalculatorPluginModel.DoesNotExist as dne:
 | 
				
			||||||
 | 
					                logger.error(
 | 
				
			||||||
 | 
					                    str(dne) + " plugin_id: " + self.request.POST['pid']
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                raise ValidationError(_('Invalid calculator properties'))
 | 
				
			||||||
 | 
					            if plugin.enable_512mb_ram:
 | 
				
			||||||
 | 
					                if value % 1 == 0 or value == 0.5:
 | 
				
			||||||
 | 
					                    logger.debug(
 | 
				
			||||||
 | 
					                        "Given ram {value} is either 0.5 or a"
 | 
				
			||||||
 | 
					                        " whole number".format(value=value)
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    if (value > 200) or (value < 0.5):
 | 
				
			||||||
 | 
					                        raise ValidationError(_('Invalid RAM size'))
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    raise ValidationError(_('Invalid RAM size'))
 | 
				
			||||||
 | 
					            elif (value > 200) or (value < 1) or (value % 1 != 0):
 | 
				
			||||||
 | 
					                raise ValidationError(_('Invalid RAM size'))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
            raise ValidationError(_('Invalid RAM size'))
 | 
					            raise ValidationError(_('Invalid RAM size'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def validate_storage(self, value):
 | 
					    def validate_storage(self, value):
 | 
				
			||||||
| 
						 | 
					@ -1203,7 +1241,7 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
 | 
				
			||||||
        cores = request.POST.get('cpu')
 | 
					        cores = request.POST.get('cpu')
 | 
				
			||||||
        cores_field = forms.IntegerField(validators=[self.validate_cores])
 | 
					        cores_field = forms.IntegerField(validators=[self.validate_cores])
 | 
				
			||||||
        memory = request.POST.get('ram')
 | 
					        memory = request.POST.get('ram')
 | 
				
			||||||
        memory_field = forms.IntegerField(validators=[self.validate_memory])
 | 
					        memory_field = forms.FloatField(validators=[self.validate_memory])
 | 
				
			||||||
        storage = request.POST.get('storage')
 | 
					        storage = request.POST.get('storage')
 | 
				
			||||||
        storage_field = forms.IntegerField(validators=[self.validate_storage])
 | 
					        storage_field = forms.IntegerField(validators=[self.validate_storage])
 | 
				
			||||||
        template_id = int(request.POST.get('config'))
 | 
					        template_id = int(request.POST.get('config'))
 | 
				
			||||||
| 
						 | 
					@ -1267,7 +1305,7 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
 | 
				
			||||||
            'price': price,
 | 
					            'price': price,
 | 
				
			||||||
            'vat': vat,
 | 
					            'vat': vat,
 | 
				
			||||||
            'vat_percent': vat_percent,
 | 
					            'vat_percent': vat_percent,
 | 
				
			||||||
            'total_price': price + vat - discount['amount'],
 | 
					            'total_price': round(price + vat - discount['amount'], 2),
 | 
				
			||||||
            'pricing_name': vm_pricing_name
 | 
					            'pricing_name': vm_pricing_name
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1394,7 +1432,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
 | 
				
			||||||
        terminated = manager.delete_vm(vm.id)
 | 
					        terminated = manager.delete_vm(vm.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not terminated:
 | 
					        if not terminated:
 | 
				
			||||||
            logger.debug(
 | 
					            logger.error(
 | 
				
			||||||
                "manager.delete_vm returned False. Hence, error making "
 | 
					                "manager.delete_vm returned False. Hence, error making "
 | 
				
			||||||
                "xml-rpc call to delete vm failed."
 | 
					                "xml-rpc call to delete vm failed."
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
| 
						 | 
					@ -1404,6 +1442,9 @@ class VirtualMachineView(LoginRequiredMixin, View):
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    manager.get_vm(vm.id)
 | 
					                    manager.get_vm(vm.id)
 | 
				
			||||||
                except WrongIdError:
 | 
					                except WrongIdError:
 | 
				
			||||||
 | 
					                    logger.error(
 | 
				
			||||||
 | 
					                        "VM {} not found. So, its terminated.".format(vm.id)
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                    response['status'] = True
 | 
					                    response['status'] = True
 | 
				
			||||||
                    response['text'] = ugettext('Terminated')
 | 
					                    response['text'] = ugettext('Terminated')
 | 
				
			||||||
                    vm_detail_obj = VMDetail.objects.filter(
 | 
					                    vm_detail_obj = VMDetail.objects.filter(
 | 
				
			||||||
| 
						 | 
					@ -1421,6 +1462,10 @@ class VirtualMachineView(LoginRequiredMixin, View):
 | 
				
			||||||
                    break
 | 
					                    break
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    sleep(2)
 | 
					                    sleep(2)
 | 
				
			||||||
 | 
					            if not response['status']:
 | 
				
			||||||
 | 
					                response['text'] = _("VM terminate action timed out. Please "
 | 
				
			||||||
 | 
					                                     "contact support@datacenterlight.ch for "
 | 
				
			||||||
 | 
					                                     "further information.")
 | 
				
			||||||
            context = {
 | 
					            context = {
 | 
				
			||||||
                'vm_name': vm_name,
 | 
					                'vm_name': vm_name,
 | 
				
			||||||
                'base_url': "{0}://{1}".format(
 | 
					                'base_url': "{0}://{1}".format(
 | 
				
			||||||
| 
						 | 
					@ -1441,11 +1486,13 @@ class VirtualMachineView(LoginRequiredMixin, View):
 | 
				
			||||||
            email = BaseEmail(**email_data)
 | 
					            email = BaseEmail(**email_data)
 | 
				
			||||||
            email.send()
 | 
					            email.send()
 | 
				
			||||||
        admin_email_body.update(response)
 | 
					        admin_email_body.update(response)
 | 
				
			||||||
 | 
					        admin_msg_sub = "VM and Subscription for VM {} and user: {}".format(
 | 
				
			||||||
 | 
					            vm.id,
 | 
				
			||||||
 | 
					            owner.email
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        email_to_admin_data = {
 | 
					        email_to_admin_data = {
 | 
				
			||||||
            'subject': "Deleted VM and Subscription for VM {vm_id} and "
 | 
					            'subject': ("Deleted " if response['status']
 | 
				
			||||||
                       "user: {user}".format(
 | 
					                        else "ERROR deleting ") + admin_msg_sub,
 | 
				
			||||||
                           vm_id=vm.id, user=owner.email
 | 
					 | 
				
			||||||
                       ),
 | 
					 | 
				
			||||||
            'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
 | 
					            'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
 | 
				
			||||||
            'to': ['info@ungleich.ch'],
 | 
					            'to': ['info@ungleich.ch'],
 | 
				
			||||||
            'body': "\n".join(
 | 
					            'body': "\n".join(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,7 +110,7 @@ class OpenNebulaManager():
 | 
				
			||||||
                raise UserExistsError()
 | 
					                raise UserExistsError()
 | 
				
			||||||
            except OpenNebulaException as err:
 | 
					            except OpenNebulaException as err:
 | 
				
			||||||
                logger.error('OpenNebulaException error: {0}'.format(err))
 | 
					                logger.error('OpenNebulaException error: {0}'.format(err))
 | 
				
			||||||
                logger.debug('User exists but password is wrong')
 | 
					                logger.error('User exists but password is wrong')
 | 
				
			||||||
                raise UserCredentialError()
 | 
					                raise UserCredentialError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        except WrongNameError:
 | 
					        except WrongNameError:
 | 
				
			||||||
| 
						 | 
					@ -148,7 +148,7 @@ class OpenNebulaManager():
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            return opennebula_user
 | 
					            return opennebula_user
 | 
				
			||||||
        except ConnectionRefusedError:
 | 
					        except ConnectionRefusedError:
 | 
				
			||||||
            logger.info(
 | 
					            logger.error(
 | 
				
			||||||
                'Could not connect to host: {host} via protocol {protocol}'.format(
 | 
					                'Could not connect to host: {host} via protocol {protocol}'.format(
 | 
				
			||||||
                    host=settings.OPENNEBULA_DOMAIN,
 | 
					                    host=settings.OPENNEBULA_DOMAIN,
 | 
				
			||||||
                    protocol=settings.OPENNEBULA_PROTOCOL)
 | 
					                    protocol=settings.OPENNEBULA_PROTOCOL)
 | 
				
			||||||
| 
						 | 
					@ -160,7 +160,7 @@ class OpenNebulaManager():
 | 
				
			||||||
            user_pool = oca.UserPool(self.oneadmin_client)
 | 
					            user_pool = oca.UserPool(self.oneadmin_client)
 | 
				
			||||||
            user_pool.info()
 | 
					            user_pool.info()
 | 
				
			||||||
        except ConnectionRefusedError:
 | 
					        except ConnectionRefusedError:
 | 
				
			||||||
            logger.info(
 | 
					            logger.error(
 | 
				
			||||||
                'Could not connect to host: {host} via protocol {protocol}'.format(
 | 
					                'Could not connect to host: {host} via protocol {protocol}'.format(
 | 
				
			||||||
                    host=settings.OPENNEBULA_DOMAIN,
 | 
					                    host=settings.OPENNEBULA_DOMAIN,
 | 
				
			||||||
                    protocol=settings.OPENNEBULA_PROTOCOL)
 | 
					                    protocol=settings.OPENNEBULA_PROTOCOL)
 | 
				
			||||||
| 
						 | 
					@ -174,7 +174,7 @@ class OpenNebulaManager():
 | 
				
			||||||
            vm_pool.info()
 | 
					            vm_pool.info()
 | 
				
			||||||
            return vm_pool
 | 
					            return vm_pool
 | 
				
			||||||
        except AttributeError:
 | 
					        except AttributeError:
 | 
				
			||||||
            logger.info('Could not connect via client, using oneadmin instead')
 | 
					            logger.error('Could not connect via client, using oneadmin instead')
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                vm_pool = oca.VirtualMachinePool(self.oneadmin_client)
 | 
					                vm_pool = oca.VirtualMachinePool(self.oneadmin_client)
 | 
				
			||||||
                vm_pool.info(filter=-2)
 | 
					                vm_pool.info(filter=-2)
 | 
				
			||||||
| 
						 | 
					@ -183,7 +183,7 @@ class OpenNebulaManager():
 | 
				
			||||||
                raise ConnectionRefusedError
 | 
					                raise ConnectionRefusedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        except ConnectionRefusedError:
 | 
					        except ConnectionRefusedError:
 | 
				
			||||||
            logger.info(
 | 
					            logger.error(
 | 
				
			||||||
                'Could not connect to host: {host} via protocol {protocol}'.format(
 | 
					                'Could not connect to host: {host} via protocol {protocol}'.format(
 | 
				
			||||||
                    host=settings.OPENNEBULA_DOMAIN,
 | 
					                    host=settings.OPENNEBULA_DOMAIN,
 | 
				
			||||||
                    protocol=settings.OPENNEBULA_PROTOCOL)
 | 
					                    protocol=settings.OPENNEBULA_PROTOCOL)
 | 
				
			||||||
| 
						 | 
					@ -249,8 +249,8 @@ class OpenNebulaManager():
 | 
				
			||||||
            vm_specs = vm_specs_formatter.format(
 | 
					            vm_specs = vm_specs_formatter.format(
 | 
				
			||||||
                vcpu=int(specs['cpu']),
 | 
					                vcpu=int(specs['cpu']),
 | 
				
			||||||
                cpu=0.1 * int(specs['cpu']),
 | 
					                cpu=0.1 * int(specs['cpu']),
 | 
				
			||||||
                memory=1024 * int(specs['memory']),
 | 
					                memory=(512 if specs['memory'] == 0.5 else
 | 
				
			||||||
 | 
					                        1024 * int(specs['memory'])),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            vm_specs += """<DISK>
 | 
					            vm_specs += """<DISK>
 | 
				
			||||||
                                  <TYPE>fs</TYPE>
 | 
					                                  <TYPE>fs</TYPE>
 | 
				
			||||||
| 
						 | 
					@ -269,8 +269,8 @@ class OpenNebulaManager():
 | 
				
			||||||
            vm_specs = vm_specs_formatter.format(
 | 
					            vm_specs = vm_specs_formatter.format(
 | 
				
			||||||
                vcpu=int(specs['cpu']),
 | 
					                vcpu=int(specs['cpu']),
 | 
				
			||||||
                cpu=0.1 * int(specs['cpu']),
 | 
					                cpu=0.1 * int(specs['cpu']),
 | 
				
			||||||
                memory=1024 * int(specs['memory']),
 | 
					                memory=(512 if specs['memory'] == 0.5 else
 | 
				
			||||||
 | 
					                        1024 * int(specs['memory'])),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            vm_specs += """<DISK>
 | 
					            vm_specs += """<DISK>
 | 
				
			||||||
                                  <TYPE>fs</TYPE>
 | 
					                                  <TYPE>fs</TYPE>
 | 
				
			||||||
| 
						 | 
					@ -325,14 +325,14 @@ class OpenNebulaManager():
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            vm_terminated = True
 | 
					            vm_terminated = True
 | 
				
			||||||
        except socket.timeout as socket_err:
 | 
					        except socket.timeout as socket_err:
 | 
				
			||||||
            logger.info("Socket timeout error: {0}".format(socket_err))
 | 
					            logger.error("Socket timeout error: {0}".format(socket_err))
 | 
				
			||||||
        except OpenNebulaException as opennebula_err:
 | 
					        except OpenNebulaException as opennebula_err:
 | 
				
			||||||
            logger.info(
 | 
					            logger.error(
 | 
				
			||||||
                "OpenNebulaException error: {0}".format(opennebula_err))
 | 
					                "OpenNebulaException error: {0}".format(opennebula_err))
 | 
				
			||||||
        except OSError as os_err:
 | 
					        except OSError as os_err:
 | 
				
			||||||
            logger.info("OSError : {0}".format(os_err))
 | 
					            logger.error("OSError : {0}".format(os_err))
 | 
				
			||||||
        except ValueError as value_err:
 | 
					        except ValueError as value_err:
 | 
				
			||||||
            logger.info("ValueError : {0}".format(value_err))
 | 
					            logger.error("ValueError : {0}".format(value_err))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return vm_terminated
 | 
					        return vm_terminated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -342,7 +342,7 @@ class OpenNebulaManager():
 | 
				
			||||||
            template_pool.info()
 | 
					            template_pool.info()
 | 
				
			||||||
            return template_pool
 | 
					            return template_pool
 | 
				
			||||||
        except ConnectionRefusedError:
 | 
					        except ConnectionRefusedError:
 | 
				
			||||||
            logger.info(
 | 
					            logger.error(
 | 
				
			||||||
                """Could not connect to host: {host} via protocol
 | 
					                """Could not connect to host: {host} via protocol
 | 
				
			||||||
                 {protocol}""".format(
 | 
					                 {protocol}""".format(
 | 
				
			||||||
                    host=settings.OPENNEBULA_DOMAIN,
 | 
					                    host=settings.OPENNEBULA_DOMAIN,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,6 @@ django-meta==1.2
 | 
				
			||||||
django-meta-mixin==0.3.0
 | 
					django-meta-mixin==0.3.0
 | 
				
			||||||
django-model-utils==2.5
 | 
					django-model-utils==2.5
 | 
				
			||||||
django-mptt==0.8.4
 | 
					django-mptt==0.8.4
 | 
				
			||||||
django-multisite==1.4.1
 | 
					 | 
				
			||||||
django-parler==1.6.3
 | 
					django-parler==1.6.3
 | 
				
			||||||
django-phonenumber-field==1.1.0
 | 
					django-phonenumber-field==1.1.0
 | 
				
			||||||
django-polymorphic==0.9.2
 | 
					django-polymorphic==0.9.2
 | 
				
			||||||
| 
						 | 
					@ -69,7 +68,7 @@ model-mommy==1.2.6
 | 
				
			||||||
phonenumbers==7.4.0
 | 
					phonenumbers==7.4.0
 | 
				
			||||||
phonenumberslite==7.4.0
 | 
					phonenumberslite==7.4.0
 | 
				
			||||||
psycopg2==2.7.3.2
 | 
					psycopg2==2.7.3.2
 | 
				
			||||||
pycryptodome==3.4
 | 
					pycryptodome==3.6.6
 | 
				
			||||||
pylibmc==1.5.1
 | 
					pylibmc==1.5.1
 | 
				
			||||||
python-dateutil==2.5.3
 | 
					python-dateutil==2.5.3
 | 
				
			||||||
python-slugify==1.2.0
 | 
					python-slugify==1.2.0
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@
 | 
				
			||||||
	    </a>
 | 
						    </a>
 | 
				
			||||||
	  </li>
 | 
						  </li>
 | 
				
			||||||
	  <li>
 | 
						  <li>
 | 
				
			||||||
	    <a href="{% url 'djangocms_blog:posts-latest-feed' %}">
 | 
						    <a href="https://blog.ungleich.ch/en-us/cms/blog/feed/">
 | 
				
			||||||
	      <span class="fa-stack fa-lg">
 | 
						      <span class="fa-stack fa-lg">
 | 
				
			||||||
		<i class="fa fa-circle fa-stack-2x"></i>
 | 
							<i class="fa fa-circle fa-stack-2x"></i>
 | 
				
			||||||
		<i class="fa fa-rss fa-stack-1x fa-inverse"></i>
 | 
							<i class="fa fa-rss fa-stack-1x fa-inverse"></i>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
import decimal
 | 
					import decimal
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from oca.pool import WrongIdError
 | 
					from oca.pool import WrongIdError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from datacenterlight.models import VMPricing
 | 
					from datacenterlight.models import VMPricing
 | 
				
			||||||
| 
						 | 
					@ -80,7 +81,7 @@ def get_vm_price(cpu, memory, disk_size, hdd_size=0, pricing_name='default'):
 | 
				
			||||||
             (decimal.Decimal(hdd_size) * pricing.hdd_unit_price))
 | 
					             (decimal.Decimal(hdd_size) * pricing.hdd_unit_price))
 | 
				
			||||||
    cents = decimal.Decimal('.01')
 | 
					    cents = decimal.Decimal('.01')
 | 
				
			||||||
    price = price.quantize(cents, decimal.ROUND_HALF_UP)
 | 
					    price = price.quantize(cents, decimal.ROUND_HALF_UP)
 | 
				
			||||||
    return float(price)
 | 
					    return round(float(price), 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0,
 | 
					def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0,
 | 
				
			||||||
| 
						 | 
					@ -126,9 +127,10 @@ def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0,
 | 
				
			||||||
    vat = vat.quantize(cents, decimal.ROUND_HALF_UP)
 | 
					    vat = vat.quantize(cents, decimal.ROUND_HALF_UP)
 | 
				
			||||||
    discount = {
 | 
					    discount = {
 | 
				
			||||||
        'name': pricing.discount_name,
 | 
					        'name': pricing.discount_name,
 | 
				
			||||||
        'amount': float(pricing.discount_amount),
 | 
					        'amount': round(float(pricing.discount_amount), 2)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return float(price), float(vat), float(vat_percent), discount
 | 
					    return (round(float(price), 2), round(float(vat), 2),
 | 
				
			||||||
 | 
					            round(float(vat_percent), 2), discount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def ping_ok(host_ipv6):
 | 
					def ping_ok(host_ipv6):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -291,7 +291,8 @@ class StripeUtils(object):
 | 
				
			||||||
        return charge
 | 
					        return charge
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None):
 | 
					    def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None,
 | 
				
			||||||
 | 
					                           price=None):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Returns the Stripe plan id string of the form
 | 
					        Returns the Stripe plan id string of the form
 | 
				
			||||||
        `dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters
 | 
					        `dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters
 | 
				
			||||||
| 
						 | 
					@ -303,6 +304,7 @@ class StripeUtils(object):
 | 
				
			||||||
        :param version: The version of the Stripe plans
 | 
					        :param version: The version of the Stripe plans
 | 
				
			||||||
        :param app: The application to which the stripe plan belongs
 | 
					        :param app: The application to which the stripe plan belongs
 | 
				
			||||||
        to. By default it is 'dcl'
 | 
					        to. By default it is 'dcl'
 | 
				
			||||||
 | 
					        :param price: The price for this plan
 | 
				
			||||||
        :return: A string of the form `dcl-v1-cpu-2-ram-5gb-ssd-10gb`
 | 
					        :return: A string of the form `dcl-v1-cpu-2-ram-5gb-ssd-10gb`
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        dcl_plan_string = 'cpu-{cpu}-ram-{ram}gb-ssd-{ssd}gb'.format(cpu=cpu,
 | 
					        dcl_plan_string = 'cpu-{cpu}-ram-{ram}gb-ssd-{ssd}gb'.format(cpu=cpu,
 | 
				
			||||||
| 
						 | 
					@ -314,19 +316,30 @@ class StripeUtils(object):
 | 
				
			||||||
        stripe_plan_id_string = '{app}-v{version}-{plan}'.format(
 | 
					        stripe_plan_id_string = '{app}-v{version}-{plan}'.format(
 | 
				
			||||||
            app=app,
 | 
					            app=app,
 | 
				
			||||||
            version=version,
 | 
					            version=version,
 | 
				
			||||||
            plan=dcl_plan_string)
 | 
					            plan=dcl_plan_string
 | 
				
			||||||
        return stripe_plan_id_string
 | 
					        )
 | 
				
			||||||
 | 
					        if price is not None:
 | 
				
			||||||
 | 
					            stripe_plan_id_string_with_price = '{}-{}chf'.format(
 | 
				
			||||||
 | 
					                stripe_plan_id_string,
 | 
				
			||||||
 | 
					                round(price, 2)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return stripe_plan_id_string_with_price
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return stripe_plan_id_string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def get_stripe_plan_name(cpu, memory, disk_size):
 | 
					    def get_stripe_plan_name(cpu, memory, disk_size, price):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Returns the Stripe plan name
 | 
					        Returns the Stripe plan name
 | 
				
			||||||
        :return:
 | 
					        :return:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD".format(
 | 
					        return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \
 | 
				
			||||||
            cpu=cpu,
 | 
					               "{price} CHF".format(
 | 
				
			||||||
            memory=memory,
 | 
					                    cpu=cpu,
 | 
				
			||||||
            disk_size=disk_size)
 | 
					                    memory=memory,
 | 
				
			||||||
 | 
					                    disk_size=disk_size,
 | 
				
			||||||
 | 
					                    price=round(price, 2)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @handleStripeError
 | 
					    @handleStripeError
 | 
				
			||||||
    def set_subscription_meta_data(self, subscription_id, meta_data):
 | 
					    def set_subscription_meta_data(self, subscription_id, meta_data):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue