merged master
This commit is contained in:
		
				commit
				
					
						a5bd8347e8
					
				
			
		
					 32 changed files with 654 additions and 165 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -42,3 +42,4 @@ secret-key
 | 
			
		|||
 | 
			
		||||
# to keep empty dirs
 | 
			
		||||
!.gitkeep
 | 
			
		||||
*.orig
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,10 @@
 | 
			
		|||
next:
 | 
			
		||||
1.7: 2018-04-20
 | 
			
		||||
    * bgfix: [all] Make /blog available on all domains
 | 
			
		||||
    * #4367: [dcl] email logo resolution fix
 | 
			
		||||
    * #4376: [cms] dcl promo section plugin link color changed to brighter shade
 | 
			
		||||
    * #4379: [dcl] pricing without VAT
 | 
			
		||||
    * bgfix: [blog] fix top menu items to show only one item
 | 
			
		||||
    * #4297: [cms] favicon as a page attribute for dcl template
 | 
			
		||||
1.6.5: 2018-04-08
 | 
			
		||||
    * #4396: [ungleich] add favicon to ungleich blog
 | 
			
		||||
    * #4327: [dcl] fix navbar logo repeat
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,18 @@
 | 
			
		|||
from django.contrib import admin
 | 
			
		||||
from cms.admin.placeholderadmin import PlaceholderAdminMixin
 | 
			
		||||
from .cms_models import CMSIntegration
 | 
			
		||||
from cms.extensions import PageExtensionAdmin
 | 
			
		||||
from .cms_models import CMSIntegration, CMSFaviconExtension
 | 
			
		||||
from .models import VMPricing
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CMSIntegrationAdmin(PlaceholderAdminMixin, admin.ModelAdmin):
 | 
			
		||||
    list_display = ('name', 'domain')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CMSFaviconExtensionAdmin(PageExtensionAdmin):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
admin.site.register(CMSIntegration, CMSIntegrationAdmin)
 | 
			
		||||
admin.site.register(CMSFaviconExtension, CMSFaviconExtensionAdmin)
 | 
			
		||||
admin.site.register(VMPricing)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,16 @@
 | 
			
		|||
from cms.extensions import PageExtension
 | 
			
		||||
from cms.extensions.extension_pool import extension_pool
 | 
			
		||||
from cms.models.fields import PlaceholderField
 | 
			
		||||
from cms.models.pluginmodel import CMSPlugin
 | 
			
		||||
from django.contrib.sites.models import Site
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.utils.safestring import mark_safe
 | 
			
		||||
from djangocms_text_ckeditor.fields import HTMLField
 | 
			
		||||
from filer.fields.file import FilerFileField
 | 
			
		||||
from filer.fields.image import FilerImageField
 | 
			
		||||
 | 
			
		||||
from datacenterlight.models import VMPricing
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CMSIntegration(models.Model):
 | 
			
		||||
    name = models.CharField(
 | 
			
		||||
| 
						 | 
				
			
			@ -30,9 +35,15 @@ class CMSIntegration(models.Model):
 | 
			
		|||
        return self.name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Models for CMS Plugins
 | 
			
		||||
class CMSFaviconExtension(PageExtension):
 | 
			
		||||
    favicon = FilerFileField(related_name="cms_favicon_image")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
extension_pool.register(CMSFaviconExtension)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Models for CMS Plugins
 | 
			
		||||
 | 
			
		||||
class DCLSectionPluginModel(CMSPlugin):
 | 
			
		||||
    heading = models.CharField(
 | 
			
		||||
        blank=True, null=True, max_length=100,
 | 
			
		||||
| 
						 | 
				
			
			@ -275,3 +286,12 @@ class DCLSectionPromoPluginModel(CMSPlugin):
 | 
			
		|||
        if self.background_image:
 | 
			
		||||
            extra_classes += ' promo-with-bg'
 | 
			
		||||
        return extra_classes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DCLCustomPricingModel(CMSPlugin):
 | 
			
		||||
    pricing = models.ForeignKey(
 | 
			
		||||
        VMPricing,
 | 
			
		||||
        related_name="dcl_custom_pricing_vm_pricing",
 | 
			
		||||
        help_text='Choose a pricing that will be associated with this '
 | 
			
		||||
                  'Calculator'
 | 
			
		||||
    )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,9 +6,9 @@ from .cms_models import (
 | 
			
		|||
    DCLFooterPluginModel, DCLLinkPluginModel, DCLNavbarDropdownPluginModel,
 | 
			
		||||
    DCLSectionIconPluginModel, DCLSectionImagePluginModel,
 | 
			
		||||
    DCLSectionPluginModel, DCLNavbarPluginModel,
 | 
			
		||||
    DCLSectionPromoPluginModel
 | 
			
		||||
    DCLSectionPromoPluginModel, DCLCustomPricingModel
 | 
			
		||||
)
 | 
			
		||||
from .models import VMTemplate
 | 
			
		||||
from .models import VMTemplate, VMPricing
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@plugin_pool.register_plugin
 | 
			
		||||
| 
						 | 
				
			
			@ -75,13 +75,13 @@ class DCLSectionPromoPlugin(CMSPluginBase):
 | 
			
		|||
@plugin_pool.register_plugin
 | 
			
		||||
class DCLCalculatorPlugin(CMSPluginBase):
 | 
			
		||||
    module = "Datacenterlight"
 | 
			
		||||
    name = "DCL Calculator Plugin"
 | 
			
		||||
    name = "DCL Calculator Section Plugin"
 | 
			
		||||
    model = DCLSectionPluginModel
 | 
			
		||||
    render_template = "datacenterlight/cms/calculator.html"
 | 
			
		||||
    cache = False
 | 
			
		||||
    allow_children = True
 | 
			
		||||
    child_classes = [
 | 
			
		||||
        'DCLSectionPromoPlugin', 'UngleichHTMLPlugin'
 | 
			
		||||
        'DCLSectionPromoPlugin', 'UngleichHTMLPlugin', 'DCLCustomPricingPlugin'
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def render(self, context, instance, placeholder):
 | 
			
		||||
| 
						 | 
				
			
			@ -89,15 +89,38 @@ class DCLCalculatorPlugin(CMSPluginBase):
 | 
			
		|||
            context, instance, placeholder
 | 
			
		||||
        )
 | 
			
		||||
        context['templates'] = VMTemplate.objects.all()
 | 
			
		||||
        context['children_to_side'] = []
 | 
			
		||||
        context['children_to_content'] = []
 | 
			
		||||
        pricing_plugin_model = None
 | 
			
		||||
        if instance.child_plugin_instances is not None:
 | 
			
		||||
            context['children_to_content'].extend(
 | 
			
		||||
                instance.child_plugin_instances
 | 
			
		||||
            )
 | 
			
		||||
            for child in instance.child_plugin_instances:
 | 
			
		||||
                if child.__class__.__name__ == 'DCLCustomPricingModel':
 | 
			
		||||
                    # The second clause is just to make sure we pick up the
 | 
			
		||||
                    # most recent CustomPricing, if more than one is present
 | 
			
		||||
                    if (pricing_plugin_model is None or child.pricing_id >
 | 
			
		||||
                            pricing_plugin_model.model.pricing_id):
 | 
			
		||||
                        pricing_plugin_model = child
 | 
			
		||||
 | 
			
		||||
        if pricing_plugin_model:
 | 
			
		||||
            context['vm_pricing'] = VMPricing.get_vm_pricing_by_name(
 | 
			
		||||
                name=pricing_plugin_model.pricing.name
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            context['vm_pricing'] = VMPricing.get_default_pricing()
 | 
			
		||||
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@plugin_pool.register_plugin
 | 
			
		||||
class DCLCustomPricingPlugin(CMSPluginBase):
 | 
			
		||||
    module = "Datacenterlight"
 | 
			
		||||
    name = "DCL Custom Pricing Plugin"
 | 
			
		||||
    model = DCLCustomPricingModel
 | 
			
		||||
    render_plugin = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@plugin_pool.register_plugin
 | 
			
		||||
class DCLBannerListPlugin(CMSPluginBase):
 | 
			
		||||
    module = "Datacenterlight"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										24
									
								
								datacenterlight/cms_toolbar.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								datacenterlight/cms_toolbar.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
from cms.extensions.toolbar import ExtensionToolbar
 | 
			
		||||
from cms.toolbar_pool import toolbar_pool
 | 
			
		||||
from django.utils.translation import ugettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from .cms_models import CMSFaviconExtension
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@toolbar_pool.register
 | 
			
		||||
class CMSFaviconExtensionToolbar(ExtensionToolbar):
 | 
			
		||||
    # defineds the model for the current toolbar
 | 
			
		||||
    model = CMSFaviconExtension
 | 
			
		||||
 | 
			
		||||
    def populate(self):
 | 
			
		||||
        # setup the extension toolbar with permissions and sanity checks
 | 
			
		||||
        current_page_menu = self._setup_extension_toolbar()
 | 
			
		||||
        # if it's all ok
 | 
			
		||||
        if current_page_menu:
 | 
			
		||||
            # retrieves the instance of the current extension (if any) and the toolbar item url
 | 
			
		||||
            page_extension, url = self.get_page_extension_admin()
 | 
			
		||||
            if url:
 | 
			
		||||
                # adds a toolbar item
 | 
			
		||||
                current_page_menu.add_modal_item(
 | 
			
		||||
                    _('CMS Favicon'), url=url, disabled=not self.toolbar.edit_mode
 | 
			
		||||
                )
 | 
			
		||||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ msgid ""
 | 
			
		|||
msgstr ""
 | 
			
		||||
"Project-Id-Version: PACKAGE VERSION\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: \n"
 | 
			
		||||
"POT-Creation-Date: 2018-03-30 21:29+0000\n"
 | 
			
		||||
"POT-Creation-Date: 2018-04-17 19:26+0000\n"
 | 
			
		||||
"PO-Revision-Date: 2018-03-30 23:22+0000\n"
 | 
			
		||||
"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
 | 
			
		||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
			
		||||
| 
						 | 
				
			
			@ -72,9 +72,9 @@ msgstr "Data Center Light Account Aktivierung"
 | 
			
		|||
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid ""
 | 
			
		||||
"You can activate your Data Center Light account by clicking <a "
 | 
			
		||||
"href=\"%(base_url)s%(activation_link)s\" style=\"text-decoration: none; "
 | 
			
		||||
"color: #4382c8; font-weight: 400;\">here</a>."
 | 
			
		||||
"You can activate your Data Center Light account by clicking <a href="
 | 
			
		||||
"\"%(base_url)s%(activation_link)s\" style=\"text-decoration: none; color: "
 | 
			
		||||
"#4382c8; font-weight: 400;\">here</a>."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Klicke <a href=\"%(base_url)s%(activation_link)s\"style=\"text-decoration: "
 | 
			
		||||
"none; color: #4382c8; font-weight: 400;\">hier</a> um deinen Data Center "
 | 
			
		||||
| 
						 | 
				
			
			@ -97,12 +97,13 @@ msgstr "Deine E-Mail-Adresse"
 | 
			
		|||
msgid "Password"
 | 
			
		||||
msgstr "Passwort"
 | 
			
		||||
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid ""
 | 
			
		||||
"You can reset your password <a href=\"%(base_url)s%(reset_password_url)s\" "
 | 
			
		||||
"style=\"text-decoration: none; color: #4382c8; font-weight: 400;\">here</a>."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Du kannst dein Passwort <a href=\"%(base_url)s%(reset_password_url)s\" "
 | 
			
		||||
"style=\"text-decoration: none; color: #4382c8; font-weight: 400;\">hier</a> "
 | 
			
		||||
"Du kannst dein Passwort <a href=\"%(base_url)s%(reset_password_url)s\" style="
 | 
			
		||||
"\"text-decoration: none; color: #4382c8; font-weight: 400;\">hier</a> "
 | 
			
		||||
"zurücksetzen."
 | 
			
		||||
 | 
			
		||||
msgid "Your Data Center Light Team"
 | 
			
		||||
| 
						 | 
				
			
			@ -160,21 +161,6 @@ msgstr "Weiter"
 | 
			
		|||
msgid "Home"
 | 
			
		||||
msgstr "Home"
 | 
			
		||||
 | 
			
		||||
msgid "Highlights"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Scale out"
 | 
			
		||||
msgstr "Skalierung"
 | 
			
		||||
 | 
			
		||||
msgid "Reliable and light"
 | 
			
		||||
msgstr "Zuverlässig und leicht"
 | 
			
		||||
 | 
			
		||||
msgid "Pricing"
 | 
			
		||||
msgstr "Preise"
 | 
			
		||||
 | 
			
		||||
msgid "Order VM"
 | 
			
		||||
msgstr "VM bestellen"
 | 
			
		||||
 | 
			
		||||
msgid "Contact"
 | 
			
		||||
msgstr "Kontakt"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -184,6 +170,9 @@ msgstr "Nutzungsbedingungen"
 | 
			
		|||
msgid "Finally, an affordable VM hosting in Switzerland!"
 | 
			
		||||
msgstr "Endlich: bezahlbares VM Hosting in der Schweiz"
 | 
			
		||||
 | 
			
		||||
msgid "Highlights"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "I want it!"
 | 
			
		||||
msgstr "Das will ich haben!"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -203,8 +192,8 @@ msgid ""
 | 
			
		|||
"order to make it more sustainable and affordable at the same time."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Ist kreativ, indem es sich ein modernes und alternatives Layout zu Nutze "
 | 
			
		||||
"macht um Nachhaltigkeit zu fördern und somit erschwingliche Preise bieten zu"
 | 
			
		||||
" können.
"
 | 
			
		||||
"macht um Nachhaltigkeit zu fördern und somit erschwingliche Preise bieten zu "
 | 
			
		||||
"können.
"
 | 
			
		||||
 | 
			
		||||
msgid ""
 | 
			
		||||
"Cuts down the costs for you by using FOSS (Free Open Source Software) "
 | 
			
		||||
| 
						 | 
				
			
			@ -214,6 +203,9 @@ msgstr ""
 | 
			
		|||
"mit FOSS (Free Open Source Software) arbeitet und wir daher auf "
 | 
			
		||||
"Lizenzgebühren verzichten können.
"
 | 
			
		||||
 | 
			
		||||
msgid "Scale out"
 | 
			
		||||
msgstr "Skalierung"
 | 
			
		||||
 | 
			
		||||
msgid ""
 | 
			
		||||
"We don't use special hardware. We use commodity hardware: we buy computers "
 | 
			
		||||
"that you buy. Just many more and put them in a cozy home for computers "
 | 
			
		||||
| 
						 | 
				
			
			@ -223,6 +215,9 @@ msgstr ""
 | 
			
		|||
"erschwingliche Systeme. Bei grösserer Auslastung werden mehr "
 | 
			
		||||
"Standardkomponenten hinzugekauft und skalieren so das Datencenter."
 | 
			
		||||
 | 
			
		||||
msgid "Reliable and light"
 | 
			
		||||
msgstr "Zuverlässig und leicht"
 | 
			
		||||
 | 
			
		||||
msgid ""
 | 
			
		||||
"Our VMs are located in Switzerland, with reliable power supply and fast "
 | 
			
		||||
"internet connection. Our VM costs less thanks to our featherlight "
 | 
			
		||||
| 
						 | 
				
			
			@ -232,8 +227,7 @@ msgstr ""
 | 
			
		|||
"Energieversorgung, sowie schneller Internetverbindung ausgestattet. Unser "
 | 
			
		||||
"Angebot ist aufgrund unserer leichten Infrastruktur überaus kostengünstig."
 | 
			
		||||
 | 
			
		||||
msgid ""
 | 
			
		||||
"Simple and affordable: Try our virtual machine with featherlight price."
 | 
			
		||||
msgid "Simple and affordable: Try our virtual machine with featherlight price."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Einfach und bezahlbar: Teste nun unsere virtuellen Maschinen mit "
 | 
			
		||||
"federleichten Preisen."
 | 
			
		||||
| 
						 | 
				
			
			@ -314,6 +308,9 @@ msgstr "Gesamt"
 | 
			
		|||
msgid "including VAT"
 | 
			
		||||
msgstr "inkl. Mehrwertsteuer"
 | 
			
		||||
 | 
			
		||||
msgid "excluding VAT"
 | 
			
		||||
msgstr "exkl. Mehrwertsteuer"
 | 
			
		||||
 | 
			
		||||
msgid "Month"
 | 
			
		||||
msgstr "Monat"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -321,20 +318,20 @@ msgid "Credit Card"
 | 
			
		|||
msgstr "Kreditkarte"
 | 
			
		||||
 | 
			
		||||
msgid ""
 | 
			
		||||
"Please fill in your credit card information below. We are using <a "
 | 
			
		||||
"href=\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do "
 | 
			
		||||
"not store your information in our database."
 | 
			
		||||
"Please fill in your credit card information below. We are using <a href="
 | 
			
		||||
"\"https://stripe.com\" target=\"_blank\">Stripe</a> for payment and do not "
 | 
			
		||||
"store your information in our database."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Bitte fülle Deine Kreditkarteninformationen unten aus. Wir nutzen <a "
 | 
			
		||||
"href=\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung "
 | 
			
		||||
"und speichern keine Informationen in unserer Datenbank."
 | 
			
		||||
"Bitte fülle Deine Kreditkarteninformationen unten aus. Wir nutzen <a href="
 | 
			
		||||
"\"https://stripe.com\" target=\"_blank\">Stripe</a> für die Bezahlung und "
 | 
			
		||||
"speichern keine Informationen in unserer Datenbank."
 | 
			
		||||
 | 
			
		||||
msgid ""
 | 
			
		||||
"You are not making any payment yet. After submitting your card information, "
 | 
			
		||||
"you will be taken to the Confirm Order Page."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst,"
 | 
			
		||||
" nachdem Du die Bestellung auf der nächsten Seite bestätigt hast."
 | 
			
		||||
"Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst, "
 | 
			
		||||
"nachdem Du die Bestellung auf der nächsten Seite bestätigt hast."
 | 
			
		||||
 | 
			
		||||
msgid "Card Number"
 | 
			
		||||
msgstr "Kreditkartennummer"
 | 
			
		||||
| 
						 | 
				
			
			@ -352,8 +349,8 @@ msgid ""
 | 
			
		|||
"You are not making any payment yet. After placing your order, you will be "
 | 
			
		||||
"taken to the Submit Payment Page."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst,"
 | 
			
		||||
" nachdem Du die Bestellung auf der nächsten Seite bestätigt hast."
 | 
			
		||||
"Es wird noch keine Bezahlung vorgenommen. Die Bezahlung wird erst ausgelöst, "
 | 
			
		||||
"nachdem Du die Bestellung auf der nächsten Seite bestätigt hast."
 | 
			
		||||
 | 
			
		||||
msgid "Processing"
 | 
			
		||||
msgstr "Weiter"
 | 
			
		||||
| 
						 | 
				
			
			@ -383,13 +380,18 @@ msgstr "Bestellungsübersicht"
 | 
			
		|||
msgid "Product"
 | 
			
		||||
msgstr "Produkt"
 | 
			
		||||
 | 
			
		||||
#, python-format
 | 
			
		||||
msgid "Subtotal"
 | 
			
		||||
msgstr "Zwischensumme"
 | 
			
		||||
 | 
			
		||||
msgid "VAT"
 | 
			
		||||
msgstr "Mehrwertsteuer"
 | 
			
		||||
 | 
			
		||||
msgid ""
 | 
			
		||||
"By clicking \"Place order\" this plan will charge your credit card account "
 | 
			
		||||
"with the fee of %(vm_price)sCHF/month"
 | 
			
		||||
"with the fee of %(vm_total_price)s CHF/month"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(vm_price)sCHF "
 | 
			
		||||
"pro Monat belastet"
 | 
			
		||||
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
 | 
			
		||||
"%(vm_total_price)s CHF pro Monat belastet"
 | 
			
		||||
 | 
			
		||||
msgid "Place order"
 | 
			
		||||
msgstr "Bestellen"
 | 
			
		||||
| 
						 | 
				
			
			@ -455,25 +457,25 @@ msgstr "Wir unterstützen die FOSS Community."
 | 
			
		|||
msgid ""
 | 
			
		||||
"Data Center Light is the child of free and open source software (FOSS) "
 | 
			
		||||
"movement. <br>We grew up with it, live by it, and believe in it.<br> The "
 | 
			
		||||
"more we work on our data center,<br> the more we contribute back to the FOSS"
 | 
			
		||||
" community."
 | 
			
		||||
"more we work on our data center,<br> the more we contribute back to the FOSS "
 | 
			
		||||
"community."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Data Center Light ist ein Teil der Free und Opens Source Software (FOSS) "
 | 
			
		||||
"Bewegung.<br/> Wir sind damit gross geworden, leben damit und glauben "
 | 
			
		||||
"daran.<br/> Je weiter wir mit unserem Data Center Light vorankommen, desto "
 | 
			
		||||
"mehr können wir etwas an die FOSS Community zurückgeben."
 | 
			
		||||
"Bewegung.<br/> Wir sind damit gross geworden, leben damit und glauben daran."
 | 
			
		||||
"<br/> Je weiter wir mit unserem Data Center Light vorankommen, desto mehr "
 | 
			
		||||
"können wir etwas an die FOSS Community zurückgeben."
 | 
			
		||||
 | 
			
		||||
msgid "We bring the future to you."
 | 
			
		||||
msgstr "Wir bringen die Zukunft zu dir."
 | 
			
		||||
 | 
			
		||||
msgid ""
 | 
			
		||||
"Data Center Light uses the most modern technologies out there.<br>Your VM "
 | 
			
		||||
"needs only IPv6. Data Center Light provides<br> transparent two-way "
 | 
			
		||||
"IPv6/IPv4 translation."
 | 
			
		||||
"needs only IPv6. Data Center Light provides<br> transparent two-way IPv6/"
 | 
			
		||||
"IPv4 translation."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Data Center Light verwendet die zur Zeit modernsten Technologien.<br/>Deine "
 | 
			
		||||
"VM läuft mit IPv6. Data Center Light bietet eine transparente "
 | 
			
		||||
"IPv6/IPv4-Zweiweglösung."
 | 
			
		||||
"VM läuft mit IPv6. Data Center Light bietet eine transparente IPv6/IPv4-"
 | 
			
		||||
"Zweiweglösung."
 | 
			
		||||
 | 
			
		||||
msgid ""
 | 
			
		||||
" No more spinning metal plates! Data Center Light uses only SSDs. We keep "
 | 
			
		||||
| 
						 | 
				
			
			@ -497,6 +499,10 @@ msgstr "Ungültige RAM-Grösse"
 | 
			
		|||
msgid "Invalid storage size"
 | 
			
		||||
msgstr "Ungültige Speicher-Grösse"
 | 
			
		||||
 | 
			
		||||
#, python-brace-format
 | 
			
		||||
msgid "Incorrect pricing name. Please contact support{support_email}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
msgid "Confirm Order"
 | 
			
		||||
msgstr "Bestellung Bestätigen"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -507,8 +513,8 @@ msgid ""
 | 
			
		|||
"There was a payment related error. On close of this popup, you will be "
 | 
			
		||||
"redirected back to the payment page."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Es ist ein Fehler bei der Zahlung betreten. Du wirst nach dem Schliessen vom"
 | 
			
		||||
" Popup zur Bezahlseite weitergeleitet."
 | 
			
		||||
"Es ist ein Fehler bei der Zahlung betreten. Du wirst nach dem Schliessen vom "
 | 
			
		||||
"Popup zur Bezahlseite weitergeleitet."
 | 
			
		||||
 | 
			
		||||
msgid "Thank you for the order."
 | 
			
		||||
msgstr "Danke für Deine Bestellung."
 | 
			
		||||
| 
						 | 
				
			
			@ -517,8 +523,14 @@ msgid ""
 | 
			
		|||
"Your VM will be up and running in a few moments. We will send you a "
 | 
			
		||||
"confirmation email as soon as it is ready."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du"
 | 
			
		||||
" auf sie zugreifen kannst."
 | 
			
		||||
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
 | 
			
		||||
"auf sie zugreifen kannst."
 | 
			
		||||
 | 
			
		||||
#~ msgid "Pricing"
 | 
			
		||||
#~ msgstr "Preise"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Order VM"
 | 
			
		||||
#~ msgstr "VM bestellen"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Enter name"
 | 
			
		||||
#~ msgstr "Name"
 | 
			
		||||
| 
						 | 
				
			
			@ -533,18 +545,19 @@ msgstr ""
 | 
			
		|||
#~ msgstr "Anfrage verschickt"
 | 
			
		||||
 | 
			
		||||
#~ msgid ""
 | 
			
		||||
#~ "Thank you for your subscription! You will receive a confirmation mail from "
 | 
			
		||||
#~ "our team"
 | 
			
		||||
#~ "Thank you for your subscription! You will receive a confirmation mail "
 | 
			
		||||
#~ "from our team"
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ "Vielen dank für Ihre Anmeldung. Sie erhalten in kürze eine Bestätigungsmail "
 | 
			
		||||
#~ "von unserem Team"
 | 
			
		||||
#~ "Vielen dank für Ihre Anmeldung. Sie erhalten in kürze eine "
 | 
			
		||||
#~ "Bestätigungsmail von unserem Team"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Thank you for your request."
 | 
			
		||||
#~ msgstr "Vielen Dank für Deine Anfrage."
 | 
			
		||||
 | 
			
		||||
#~ msgid "You are one step away from being our beta tester!"
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ "Sie sind nur noch einen Schritt davon entfernt, unser Beta-Tester zu werden!"
 | 
			
		||||
#~ "Sie sind nur noch einen Schritt davon entfernt, unser Beta-Tester zu "
 | 
			
		||||
#~ "werden!"
 | 
			
		||||
 | 
			
		||||
#~ msgid ""
 | 
			
		||||
#~ "Currently we are running our tests to make sure everything runs perfectly."
 | 
			
		||||
| 
						 | 
				
			
			@ -553,8 +566,8 @@ msgstr ""
 | 
			
		|||
#~ "sicherzustellen."
 | 
			
		||||
 | 
			
		||||
#~ msgid ""
 | 
			
		||||
#~ "In the meantime, we would like to ask you a little patience<br/> until our "
 | 
			
		||||
#~ "team contacts you with beta access."
 | 
			
		||||
#~ "In the meantime, we would like to ask you a little patience<br/> until "
 | 
			
		||||
#~ "our team contacts you with beta access."
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ "Wir werden dann sobald als möglich Ihren Beta-Zugang erstellen und Sie "
 | 
			
		||||
#~ "daraufhin kontaktieren.Bis dahin bitten wir Sie um etwas Geduld."
 | 
			
		||||
| 
						 | 
				
			
			@ -564,8 +577,8 @@ msgstr ""
 | 
			
		|||
 | 
			
		||||
#~ msgid "Thank you for order! Our team will contact you via email"
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ "Vielen Dank für die Bestellung. Unser Team setzt sich sobald wie möglich mit"
 | 
			
		||||
#~ " Dir via E-Mail in Verbindung."
 | 
			
		||||
#~ "Vielen Dank für die Bestellung. Unser Team setzt sich sobald wie möglich "
 | 
			
		||||
#~ "mit Dir via E-Mail in Verbindung."
 | 
			
		||||
 | 
			
		||||
#~ msgid "Affordable VM hosting based in Switzerland"
 | 
			
		||||
#~ msgstr "Bezahlbares VM Hosting in der Schweiz"
 | 
			
		||||
| 
						 | 
				
			
			@ -581,18 +594,18 @@ msgstr ""
 | 
			
		|||
 | 
			
		||||
#~ msgid ""
 | 
			
		||||
#~ "Our VMs are hosted in Glarus, Switzerland, and our website is currently "
 | 
			
		||||
#~ "running in BETA mode. If you want more information that you did not find on "
 | 
			
		||||
#~ "our website, or if your order is more detailed, or if you encounter any "
 | 
			
		||||
#~ "technical hiccups, please contact us at support@datacenterlight.ch, our team"
 | 
			
		||||
#~ " will get in touch with you asap."
 | 
			
		||||
#~ "running in BETA mode. If you want more information that you did not find "
 | 
			
		||||
#~ "on our website, or if your order is more detailed, or if you encounter "
 | 
			
		||||
#~ "any technical hiccups, please contact us at support@datacenterlight.ch, "
 | 
			
		||||
#~ "our team will get in touch with you asap."
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ "Unsere VMs werden in der Schweiz im Kanton Glarus gehostet und befinden sich"
 | 
			
		||||
#~ " zur Zeit noch in der BETA-Phase. Möchtest du mehr über uns erfahren und "
 | 
			
		||||
#~ "hast auf unserer Website nicht genügend Informationen gefunden? Möchtest "
 | 
			
		||||
#~ "eine detailliertere Bestellung aufgeben? Bist du auf technische Probleme "
 | 
			
		||||
#~ "gestossen, die du uns mitteilen möchtest? Dann zögere nicht und kontaktiere "
 | 
			
		||||
#~ "uns unter support@datacenterlight.ch. Unser Team wird sich umgehend um dein "
 | 
			
		||||
#~ "Anliegen kümmern!"
 | 
			
		||||
#~ "Unsere VMs werden in der Schweiz im Kanton Glarus gehostet und befinden "
 | 
			
		||||
#~ "sich zur Zeit noch in der BETA-Phase. Möchtest du mehr über uns erfahren "
 | 
			
		||||
#~ "und hast auf unserer Website nicht genügend Informationen gefunden? "
 | 
			
		||||
#~ "Möchtest eine detailliertere Bestellung aufgeben? Bist du auf technische "
 | 
			
		||||
#~ "Probleme gestossen, die du uns mitteilen möchtest? Dann zögere nicht und "
 | 
			
		||||
#~ "kontaktiere uns unter support@datacenterlight.ch. Unser Team wird sich "
 | 
			
		||||
#~ "umgehend um dein Anliegen kümmern!"
 | 
			
		||||
 | 
			
		||||
#~ msgid "is not a proper name"
 | 
			
		||||
#~ msgstr "ist kein gültiger Name"
 | 
			
		||||
| 
						 | 
				
			
			@ -610,12 +623,14 @@ msgstr ""
 | 
			
		|||
#~ "\n"
 | 
			
		||||
#~ "Hi,\n"
 | 
			
		||||
#~ "\n"
 | 
			
		||||
#~ "You can activate your %(dcl_text)s account by clicking here %(base_url)s%(activation_link)s\n"
 | 
			
		||||
#~ "You can activate your %(dcl_text)s account by clicking here %(base_url)s"
 | 
			
		||||
#~ "%(activation_link)s\n"
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ "\n"
 | 
			
		||||
#~ "Hallo,\n"
 | 
			
		||||
#~ "\n"
 | 
			
		||||
#~ "Du kannst deinen %(dcl_text)s Account aktivieren, indem du hier klickst %(base_url)s%(activation_link)s\n"
 | 
			
		||||
#~ "Du kannst deinen %(dcl_text)s Account aktivieren, indem du hier klickst "
 | 
			
		||||
#~ "%(base_url)s%(activation_link)s\n"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Your"
 | 
			
		||||
#~ msgstr "Dein"
 | 
			
		||||
| 
						 | 
				
			
			@ -650,12 +665,14 @@ msgstr ""
 | 
			
		|||
#~ msgid "I want to have it!"
 | 
			
		||||
#~ msgstr "Das möchte ich haben!"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Reuse existing factory halls intead of building an expensive building."
 | 
			
		||||
#~ msgid ""
 | 
			
		||||
#~ "Reuse existing factory halls intead of building an expensive building."
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ "Nachhaltigkeit: Wiederverwendung ehemaliger Fabrikhallen an Stelle der "
 | 
			
		||||
#~ "Errichtung eines neuen Gebäudes"
 | 
			
		||||
 | 
			
		||||
#~ msgid "Being creative, using modern and alternative design for a datacenter."
 | 
			
		||||
#~ msgid ""
 | 
			
		||||
#~ "Being creative, using modern and alternative design for a datacenter."
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ "Kreativität: Verwendung eines modernen und alternativen Designs für unser "
 | 
			
		||||
#~ "Datencenter"
 | 
			
		||||
| 
						 | 
				
			
			@ -678,8 +695,8 @@ msgstr ""
 | 
			
		|||
#~ msgstr "Standort des Datacenters ist in der Schweiz"
 | 
			
		||||
 | 
			
		||||
#~ msgid ""
 | 
			
		||||
#~ " WARNING: We are currently running in BETA mode. We hope you won't encounter"
 | 
			
		||||
#~ " any hiccups, but if you do, please let us know at "
 | 
			
		||||
#~ " WARNING: We are currently running in BETA mode. We hope you won't "
 | 
			
		||||
#~ "encounter any hiccups, but if you do, please let us know at "
 | 
			
		||||
#~ "support@datacenterlight.ch"
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ " Achtung: Wir befinden uns zurzeit im Beta-Release. Wir hoffen, dass Sie "
 | 
			
		||||
| 
						 | 
				
			
			@ -693,8 +710,8 @@ msgstr ""
 | 
			
		|||
#~ msgstr "Unser Versprechen"
 | 
			
		||||
 | 
			
		||||
#~ msgid ""
 | 
			
		||||
#~ "Instead of creating an expensive SLA for availability, we promise that we do"
 | 
			
		||||
#~ " our best to run things as smooth as possible."
 | 
			
		||||
#~ "Instead of creating an expensive SLA for availability, we promise that we "
 | 
			
		||||
#~ "do our best to run things as smooth as possible."
 | 
			
		||||
#~ msgstr ""
 | 
			
		||||
#~ "Anstatt eines SLAs (Service Levle Agreements) zu vereinbaren,setzen wir "
 | 
			
		||||
#~ "unsere persönliche Arbeitskraft ein, um Ihnen ein sorgenfreiesHosting zu "
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
from django.core.management.base import BaseCommand
 | 
			
		||||
 | 
			
		||||
from datacenterlight.models import VMPricing
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Command(BaseCommand):
 | 
			
		||||
    help = '''Creates default VMPricing object'''
 | 
			
		||||
    DEFAULT_VMPRICING_NAME = 'default'
 | 
			
		||||
 | 
			
		||||
    def handle(self, *args, **options):
 | 
			
		||||
        self.create_default_vm_pricing()
 | 
			
		||||
 | 
			
		||||
    def create_default_vm_pricing(self):
 | 
			
		||||
        obj, created = VMPricing.objects.get_or_create(
 | 
			
		||||
            name=self.DEFAULT_VMPRICING_NAME,
 | 
			
		||||
            defaults={
 | 
			
		||||
                "vat_inclusive": True,
 | 
			
		||||
                "cores_unit_price": 5,
 | 
			
		||||
                "ram_unit_price": 2,
 | 
			
		||||
                "ssd_unit_price": 0.6,
 | 
			
		||||
                "hdd_unit_price": 0.01
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if created:
 | 
			
		||||
            print(
 | 
			
		||||
                'Successfully created {} VMPricing object'.format(
 | 
			
		||||
                    self.DEFAULT_VMPRICING_NAME
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            print(
 | 
			
		||||
                '{} VMPricing exists already.'.format(
 | 
			
		||||
                    self.DEFAULT_VMPRICING_NAME
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
							
								
								
									
										45
									
								
								datacenterlight/migrations/0019_auto_20180415_2236.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								datacenterlight/migrations/0019_auto_20180415_2236.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
# Generated by Django 1.9.4 on 2018-04-15 22:36
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('cms', '0014_auto_20160404_1908'),
 | 
			
		||||
        ('datacenterlight', '0018_auto_20180403_1930'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='DCLCustomPricingModel',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('cmsplugin_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cms.CMSPlugin')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
            },
 | 
			
		||||
            bases=('cms.cmsplugin',),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='VMPricing',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('name', models.CharField(max_length=255, unique=True)),
 | 
			
		||||
                ('vat_inclusive', models.BooleanField(default=True)),
 | 
			
		||||
                ('vat_percentage', models.DecimalField(blank=True, decimal_places=5, default=0, max_digits=7)),
 | 
			
		||||
                ('cores_unit_price', models.DecimalField(decimal_places=5, default=0, max_digits=7)),
 | 
			
		||||
                ('ram_unit_price', models.DecimalField(decimal_places=5, default=0, max_digits=7)),
 | 
			
		||||
                ('ssd_unit_price', models.DecimalField(decimal_places=5, default=0, max_digits=7)),
 | 
			
		||||
                ('hdd_unit_price', models.DecimalField(decimal_places=6, default=0, max_digits=7)),
 | 
			
		||||
            ],
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='dclcustompricingmodel',
 | 
			
		||||
            name='pricing',
 | 
			
		||||
            field=models.ForeignKey(help_text='Choose a pricing that will be associated with this Calculator', on_delete=django.db.models.deletion.CASCADE, related_name='dcl_custom_pricing_vm_pricing', to='datacenterlight.VMPricing'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
							
								
								
									
										29
									
								
								datacenterlight/migrations/0019_cmsfaviconextension.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								datacenterlight/migrations/0019_cmsfaviconextension.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
# Generated by Django 1.9.4 on 2018-04-12 03:16
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import filer.fields.file
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('datacenterlight', '0018_auto_20180403_1930'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='CMSFaviconExtension',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('extended_object', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='cms.Page')),
 | 
			
		||||
                ('favicon', filer.fields.file.FilerFileField(on_delete=django.db.models.deletion.CASCADE, related_name='cms_favicon_image', to='filer.File')),
 | 
			
		||||
                ('public_extension', models.OneToOneField(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='draft_extension', to='datacenterlight.CMSFaviconExtension')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
							
								
								
									
										16
									
								
								datacenterlight/migrations/0020_merge.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								datacenterlight/migrations/0020_merge.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
# Generated by Django 1.9.4 on 2018-04-20 15:04
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from django.db import migrations
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('datacenterlight', '0019_auto_20180415_2236'),
 | 
			
		||||
        ('datacenterlight', '0019_cmsfaviconextension'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,9 @@
 | 
			
		|||
import logging
 | 
			
		||||
 | 
			
		||||
from django.db import models
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VMTemplate(models.Model):
 | 
			
		||||
    name = models.CharField(max_length=50)
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +16,59 @@ class VMTemplate(models.Model):
 | 
			
		|||
        return vm_template
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VMPricing(models.Model):
 | 
			
		||||
    name = models.CharField(max_length=255, unique=True)
 | 
			
		||||
    vat_inclusive = models.BooleanField(default=True)
 | 
			
		||||
    vat_percentage = models.DecimalField(
 | 
			
		||||
        max_digits=7, decimal_places=5, blank=True, default=0
 | 
			
		||||
    )
 | 
			
		||||
    cores_unit_price = models.DecimalField(
 | 
			
		||||
        max_digits=7, decimal_places=5, default=0
 | 
			
		||||
    )
 | 
			
		||||
    ram_unit_price = models.DecimalField(
 | 
			
		||||
        max_digits=7, decimal_places=5, default=0
 | 
			
		||||
    )
 | 
			
		||||
    ssd_unit_price = models.DecimalField(
 | 
			
		||||
        max_digits=7, decimal_places=5, default=0
 | 
			
		||||
    )
 | 
			
		||||
    hdd_unit_price = models.DecimalField(
 | 
			
		||||
        max_digits=7, decimal_places=6, default=0
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.name + ' => ' + ' - '.join([
 | 
			
		||||
            '{}/Core'.format(self.cores_unit_price.normalize()),
 | 
			
		||||
            '{}/GB RAM'.format(self.ram_unit_price.normalize()),
 | 
			
		||||
            '{}/GB SSD'.format(self.ssd_unit_price.normalize()),
 | 
			
		||||
            '{}/GB HDD'.format(self.hdd_unit_price.normalize()),
 | 
			
		||||
            '{}% VAT'.format(self.vat_percentage.normalize())
 | 
			
		||||
            if not self.vat_inclusive else 'VAT-Incl', ]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_vm_pricing_by_name(cls, name):
 | 
			
		||||
        try:
 | 
			
		||||
            pricing = VMPricing.objects.get(name=name)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(
 | 
			
		||||
                "Error getting VMPricing with name {name}. "
 | 
			
		||||
                "Details: {details}. Attempting to return default"
 | 
			
		||||
                "pricing.".format(name=name, details=str(e))
 | 
			
		||||
            )
 | 
			
		||||
            pricing = VMPricing.get_default_pricing()
 | 
			
		||||
        return pricing
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def get_default_pricing(cls):
 | 
			
		||||
        """ Returns the default pricing or None """
 | 
			
		||||
        try:
 | 
			
		||||
            default_pricing = VMPricing.objects.get(name='default')
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            logger.error(str(e))
 | 
			
		||||
            default_pricing = None
 | 
			
		||||
        return default_pricing
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StripePlan(models.Model):
 | 
			
		||||
    """
 | 
			
		||||
    A model to store Data Center Light's created Stripe plans
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -120,6 +120,11 @@
 | 
			
		|||
    .header_slider .intro-cap {
 | 
			
		||||
        font-size: 3.25em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .header_slider > .carousel .item .container {
 | 
			
		||||
        padding-left: 0;
 | 
			
		||||
        padding-right: 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.header_slider .intro_lead {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -171,7 +171,18 @@
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    function _calcPricing() {
 | 
			
		||||
        var total = (cardPricing['cpu'].value * 5) + (2 * cardPricing['ram'].value) + (0.6 * cardPricing['storage'].value);
 | 
			
		||||
        if(typeof window.coresUnitPrice === 'undefined'){
 | 
			
		||||
            window.coresUnitPrice = 5;
 | 
			
		||||
        }
 | 
			
		||||
        if(typeof window.ramUnitPrice === 'undefined'){
 | 
			
		||||
            window.coresUnitPrice = 2;
 | 
			
		||||
        }
 | 
			
		||||
        if(typeof window.ssdUnitPrice === 'undefined'){
 | 
			
		||||
            window.ssdUnitPrice = 0.6;
 | 
			
		||||
        }
 | 
			
		||||
        var total = (cardPricing['cpu'].value * window.coresUnitPrice) +
 | 
			
		||||
                    (cardPricing['ram'].value * window.ramUnitPrice) +
 | 
			
		||||
                    (cardPricing['storage'].value * window.ssdUnitPrice);
 | 
			
		||||
        total = parseFloat(total.toFixed(2));
 | 
			
		||||
        $("#total").text(total);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,8 @@ from utils.hosting_utils import get_all_public_keys, get_or_create_vm_detail
 | 
			
		|||
from utils.mailer import BaseEmail
 | 
			
		||||
from utils.stripe_utils import StripeUtils
 | 
			
		||||
 | 
			
		||||
from .models import VMPricing
 | 
			
		||||
 | 
			
		||||
logger = get_task_logger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -53,6 +55,11 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
 | 
			
		|||
        "Running create_vm_task on {}".format(current_task.request.hostname))
 | 
			
		||||
    vm_id = None
 | 
			
		||||
    try:
 | 
			
		||||
        final_price = (
 | 
			
		||||
            specs.get('total_price') if 'total_price' in specs
 | 
			
		||||
            else specs.get('price')
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if 'pass' in user:
 | 
			
		||||
            on_user = user.get('email')
 | 
			
		||||
            on_pass = user.get('pass')
 | 
			
		||||
| 
						 | 
				
			
			@ -126,7 +133,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
 | 
			
		|||
            'cores': specs.get('cpu'),
 | 
			
		||||
            'memory': specs.get('memory'),
 | 
			
		||||
            'storage': specs.get('disk_size'),
 | 
			
		||||
            'price': specs.get('price'),
 | 
			
		||||
            'price': final_price,
 | 
			
		||||
            'template': template.get('name'),
 | 
			
		||||
            'vm_name': vm.get('name'),
 | 
			
		||||
            'vm_id': vm['vm_id'],
 | 
			
		||||
| 
						 | 
				
			
			@ -135,6 +142,10 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
 | 
			
		|||
 | 
			
		||||
        if error_msg:
 | 
			
		||||
            context['errors'] = error_msg
 | 
			
		||||
        if 'pricing_name' in specs:
 | 
			
		||||
            context['pricing'] = str(VMPricing.get_vm_pricing_by_name(
 | 
			
		||||
                name=specs['pricing_name']
 | 
			
		||||
            ))
 | 
			
		||||
        email_data = {
 | 
			
		||||
            'subject': settings.DCL_TEXT + " Order from %s" % context['email'],
 | 
			
		||||
            'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,9 +8,9 @@
 | 
			
		|||
    <meta charset="utf-8">
 | 
			
		||||
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
    <meta name="description" content="Data Center Light by ungleich">
 | 
			
		||||
    <meta name="description" content="{% page_attribute 'meta_description' %}">
 | 
			
		||||
    <meta name="author" content="ungleich glarus ag">
 | 
			
		||||
    <title>{% page_attribute page_title %}</title>
 | 
			
		||||
    <title>{% page_attribute "page_title" %}</title>
 | 
			
		||||
 | 
			
		||||
    <!-- Vendor CSS -->
 | 
			
		||||
    <!-- Bootstrap Core CSS -->
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +30,11 @@
 | 
			
		|||
    <!-- External Fonts -->
 | 
			
		||||
    <link href="//fonts.googleapis.com/css?family=Lato:300,400,600,700" rel="stylesheet" type="text/css">
 | 
			
		||||
 | 
			
		||||
    {% if request.current_page.cmsfaviconextension %}
 | 
			
		||||
        <link rel="shortcut icon" href="{% static request.current_page.cmsfaviconextension.favicon.url %}" type="image/x-icon">
 | 
			
		||||
    {% else %}
 | 
			
		||||
        <link rel="shortcut icon" href="{% static 'datacenterlight/img/favicon.ico' %}" type="image/x-icon">
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
 | 
			
		||||
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
 | 
			
		||||
| 
						 | 
				
			
			@ -52,7 +56,7 @@
 | 
			
		|||
    {% placeholder 'Datacenterlight Header' or %}
 | 
			
		||||
        <div class="dcl-header">
 | 
			
		||||
            <div class="container">
 | 
			
		||||
                <h1>{% page_attribute page_title %}</h1>
 | 
			
		||||
                <h1>{% page_attribute "page_title" %}</h1>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endplaceholder %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,16 @@
 | 
			
		|||
{% load staticfiles i18n%}
 | 
			
		||||
 | 
			
		||||
{% if vm_pricing %}
 | 
			
		||||
    <script type="application/javascript">
 | 
			
		||||
        window.vat_inclusive = {% if vm_pricing.vat_inclusive %}true{% else %}false{% endif%};
 | 
			
		||||
        window.vat_percentage = {{vm_pricing.vat_percentage|default:0}};
 | 
			
		||||
        window.coresUnitPrice = {{vm_pricing.cores_unit_price|default:0}};
 | 
			
		||||
        window.ramUnitPrice = {{vm_pricing.ram_unit_price|default:0}};
 | 
			
		||||
        window.ssdUnitPrice = {{vm_pricing.ssd_unit_price|default:0}};
 | 
			
		||||
        window.hddUnitPrice = {{vm_pricing.hdd_unit_price|default:0}};
 | 
			
		||||
    </script>
 | 
			
		||||
{% endif %}
 | 
			
		||||
 | 
			
		||||
<form id="order_form" method="POST" action="{% url 'datacenterlight:index' %}" data-toggle="validator" role="form">
 | 
			
		||||
    {% csrf_token %}
 | 
			
		||||
    <div class="title">
 | 
			
		||||
| 
						 | 
				
			
			@ -7,9 +19,11 @@
 | 
			
		|||
    <div class="price">
 | 
			
		||||
        <span id="total">15</span>
 | 
			
		||||
        <span>CHF/{% trans "month" %}</span>
 | 
			
		||||
        {% if vm_pricing.vat_inclusive %}
 | 
			
		||||
        <div class="price-text">
 | 
			
		||||
            <p>{% trans "VAT included" %}</p>
 | 
			
		||||
        </div>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="descriptions">
 | 
			
		||||
        <div class="description form-group">
 | 
			
		||||
| 
						 | 
				
			
			@ -78,5 +92,6 @@
 | 
			
		|||
            </select>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <input type="hidden" name="pricing_name" value="{% if vm_pricing.name %}{{vm_pricing.name}}{% else %}unknown{% endif%}"></input>
 | 
			
		||||
    <input type="submit" class="btn btn-primary disabled" value="{% trans 'Continue' %}"></input>
 | 
			
		||||
</form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,7 +78,7 @@
 | 
			
		|||
                        <hr>
 | 
			
		||||
                        <p>{% trans "Configuration"%} <strong class="pull-right">{{request.session.template.name}}</strong></p>
 | 
			
		||||
                        <hr>
 | 
			
		||||
                        <p class="last-p"><strong>{%trans "Total" %}</strong>  <small>({%trans "including VAT" %})</small> <strong class="pull-right">{{request.session.specs.price|intcomma}} CHF/{% trans "Month" %}</strong></p>
 | 
			
		||||
                        <p class="last-p"><strong>{%trans "Total" %}</strong>  <small>({% if vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %})</small> <strong class="pull-right">{{request.session.specs.price|intcomma}} CHF/{% trans "Month" %}</strong></p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,9 +65,19 @@
 | 
			
		|||
                            <span>{% trans "Disk space" %}: </span>
 | 
			
		||||
                            <span class="pull-right">{{vm.disk_size|intcomma}} GB</span>
 | 
			
		||||
                        </p>
 | 
			
		||||
                        {% if vm.vat > 0 %}
 | 
			
		||||
                            <p>
 | 
			
		||||
                            <span>{% trans "Total" %}</span>
 | 
			
		||||
                            <span class="pull-right">{{vm.price|intcomma}} CHF</span>
 | 
			
		||||
                                <strong>{% trans "Subtotal" %}: </strong>
 | 
			
		||||
                                <span class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</span>
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <p>
 | 
			
		||||
                                <span>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%): </span>
 | 
			
		||||
                                <span class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</span>
 | 
			
		||||
                            </p>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        <p>
 | 
			
		||||
                            <strong>{% trans "Total" %}</strong>
 | 
			
		||||
                            <span class="pull-right">{{vm.total_price|floatformat:2|intcomma}} CHF</span>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -78,7 +88,7 @@
 | 
			
		|||
            {% csrf_token %}
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-sm-8">
 | 
			
		||||
                    <div class="dcl-place-order-text">{% blocktrans with vm_price=request.session.specs.price %}By clicking "Place order" this plan will charge your credit card account with the fee of {{ vm_price }}CHF/month{% endblocktrans %}.</div>
 | 
			
		||||
                    <div class="dcl-place-order-text">{% blocktrans with vm_total_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with the fee of {{vm_total_price}} CHF/month{% endblocktrans %}.</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-sm-4 order-confirm-btn text-right">
 | 
			
		||||
                    <button class="btn choice-btn" id="btn-create-vm" data-toggle="modal" data-target="#createvm-modal">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ from hosting.models import HostingOrder, HostingBill
 | 
			
		|||
from membership.models import StripeCustomer
 | 
			
		||||
from utils.forms import UserBillingAddressForm
 | 
			
		||||
from utils.models import BillingAddress
 | 
			
		||||
from .models import VMPricing
 | 
			
		||||
from .cms_models import CMSIntegration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -30,16 +31,26 @@ def create_vm(billing_address_data, stripe_customer_id, specs,
 | 
			
		|||
        country=billing_address_data['country']
 | 
			
		||||
    )
 | 
			
		||||
    billing_address.save()
 | 
			
		||||
 | 
			
		||||
    customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
 | 
			
		||||
    vm_pricing = (
 | 
			
		||||
        VMPricing.get_vm_pricing_by_name(name=specs['pricing_name'])
 | 
			
		||||
        if 'pricing_name' in specs else
 | 
			
		||||
        VMPricing.get_default_pricing()
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    final_price = (
 | 
			
		||||
        specs.get('total_price')
 | 
			
		||||
        if 'total_price' in specs
 | 
			
		||||
        else specs.get('price')
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # Create a Hosting Order with vm_id = 0, we shall set it later in
 | 
			
		||||
    # celery task once the VM instance is up and running
 | 
			
		||||
    order = HostingOrder.create(
 | 
			
		||||
        price=specs['price'],
 | 
			
		||||
        vm_id=0,
 | 
			
		||||
        price=final_price,
 | 
			
		||||
        customer=customer,
 | 
			
		||||
        billing_address=billing_address
 | 
			
		||||
        billing_address=billing_address,
 | 
			
		||||
        vm_pricing=vm_pricing
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # Create a Hosting Bill
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,11 +17,11 @@ from hosting.models import HostingOrder
 | 
			
		|||
from membership.models import CustomUser, StripeCustomer
 | 
			
		||||
from opennebula_api.serializers import VMTemplateSerializer
 | 
			
		||||
from utils.forms import BillingAddressForm, BillingAddressFormSignup
 | 
			
		||||
from utils.hosting_utils import get_vm_price
 | 
			
		||||
from utils.hosting_utils import get_vm_price_with_vat
 | 
			
		||||
from utils.stripe_utils import StripeUtils
 | 
			
		||||
from utils.tasks import send_plain_email_task
 | 
			
		||||
from .forms import ContactForm
 | 
			
		||||
from .models import VMTemplate
 | 
			
		||||
from .models import VMTemplate, VMPricing
 | 
			
		||||
from .utils import get_cms_integration, create_vm
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
| 
						 | 
				
			
			@ -91,7 +91,8 @@ class IndexView(CreateView):
 | 
			
		|||
 | 
			
		||||
    @cache_control(no_cache=True, must_revalidate=True, no_store=True)
 | 
			
		||||
    def get(self, request, *args, **kwargs):
 | 
			
		||||
        for session_var in ['specs', 'user', 'billing_address_data']:
 | 
			
		||||
        for session_var in ['specs', 'user', 'billing_address_data',
 | 
			
		||||
                            'pricing_name']:
 | 
			
		||||
            if session_var in request.session:
 | 
			
		||||
                del request.session[session_var]
 | 
			
		||||
        return HttpResponseRedirect(reverse('datacenterlight:cms_index'))
 | 
			
		||||
| 
						 | 
				
			
			@ -104,12 +105,30 @@ class IndexView(CreateView):
 | 
			
		|||
        storage = request.POST.get('storage')
 | 
			
		||||
        storage_field = forms.IntegerField(validators=[self.validate_storage])
 | 
			
		||||
        template_id = int(request.POST.get('config'))
 | 
			
		||||
        pricing_name = request.POST.get('pricing_name')
 | 
			
		||||
        vm_pricing = VMPricing.get_vm_pricing_by_name(pricing_name)
 | 
			
		||||
 | 
			
		||||
        template = VMTemplate.objects.filter(
 | 
			
		||||
            opennebula_vm_template_id=template_id
 | 
			
		||||
        ).first()
 | 
			
		||||
        template_data = VMTemplateSerializer(template).data
 | 
			
		||||
        referer_url = request.META['HTTP_REFERER']
 | 
			
		||||
 | 
			
		||||
        if vm_pricing is None:
 | 
			
		||||
            vm_pricing_name_msg = _(
 | 
			
		||||
                "Incorrect pricing name. Please contact support"
 | 
			
		||||
                "{support_email}".format(
 | 
			
		||||
                    support_email=settings.DCL_SUPPORT_FROM_ADDRESS
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            messages.add_message(
 | 
			
		||||
                self.request, messages.ERROR, vm_pricing_name_msg,
 | 
			
		||||
                extra_tags='pricing'
 | 
			
		||||
            )
 | 
			
		||||
            return HttpResponseRedirect(referer_url + "#order_form")
 | 
			
		||||
        else:
 | 
			
		||||
            vm_pricing_name = vm_pricing.name
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            cores = cores_field.clean(cores)
 | 
			
		||||
        except ValidationError as err:
 | 
			
		||||
| 
						 | 
				
			
			@ -137,14 +156,21 @@ class IndexView(CreateView):
 | 
			
		|||
            )
 | 
			
		||||
            return HttpResponseRedirect(referer_url + "#order_form")
 | 
			
		||||
 | 
			
		||||
        amount_to_be_charged = get_vm_price(
 | 
			
		||||
            cpu=cores, memory=memory, disk_size=storage
 | 
			
		||||
        price, vat, vat_percent = get_vm_price_with_vat(
 | 
			
		||||
            cpu=cores,
 | 
			
		||||
            memory=memory,
 | 
			
		||||
            ssd_size=storage,
 | 
			
		||||
            pricing_name=vm_pricing_name
 | 
			
		||||
        )
 | 
			
		||||
        specs = {
 | 
			
		||||
            'cpu': cores,
 | 
			
		||||
            'memory': memory,
 | 
			
		||||
            'disk_size': storage,
 | 
			
		||||
            'price': amount_to_be_charged
 | 
			
		||||
            'price': price,
 | 
			
		||||
            'vat': vat,
 | 
			
		||||
            'vat_percent': vat_percent,
 | 
			
		||||
            'total_price': price + vat,
 | 
			
		||||
            'pricing_name': vm_pricing_name
 | 
			
		||||
        }
 | 
			
		||||
        request.session['specs'] = specs
 | 
			
		||||
        request.session['template'] = template_data
 | 
			
		||||
| 
						 | 
				
			
			@ -218,7 +244,10 @@ class PaymentOrderView(FormView):
 | 
			
		|||
            'site_url': reverse('datacenterlight:index'),
 | 
			
		||||
            'login_form': HostingUserLoginForm(prefix='login_form'),
 | 
			
		||||
            'billing_address_form': billing_address_form,
 | 
			
		||||
            'cms_integration': get_cms_integration('default')
 | 
			
		||||
            'cms_integration': get_cms_integration('default'),
 | 
			
		||||
            'vm_pricing': VMPricing.get_vm_pricing_by_name(
 | 
			
		||||
                self.request.session['specs']['pricing_name']
 | 
			
		||||
            )
 | 
			
		||||
        })
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -391,7 +420,7 @@ class OrderConfirmationView(DetailView):
 | 
			
		|||
        cpu = specs.get('cpu')
 | 
			
		||||
        memory = specs.get('memory')
 | 
			
		||||
        disk_size = specs.get('disk_size')
 | 
			
		||||
        amount_to_be_charged = specs.get('price')
 | 
			
		||||
        amount_to_be_charged = specs.get('total_price')
 | 
			
		||||
        plan_name = StripeUtils.get_stripe_plan_name(cpu=cpu,
 | 
			
		||||
                                                     memory=memory,
 | 
			
		||||
                                                     disk_size=disk_size)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,7 +70,7 @@ hr.small {
 | 
			
		|||
  }
 | 
			
		||||
  .navbar-custom .navbar-brand {
 | 
			
		||||
    color: white;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    padding: 5px 20px;
 | 
			
		||||
  }
 | 
			
		||||
  .navbar-custom .navbar-brand:hover,
 | 
			
		||||
  .navbar-custom .navbar-brand:focus {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -516,7 +516,7 @@ META_INCLUDE_KEYWORDS = ["ungleich", "hosting", "switzerland",
 | 
			
		|||
                         "Schweiz", "Swiss", "cdist"]
 | 
			
		||||
META_USE_SITES = True
 | 
			
		||||
 | 
			
		||||
PARLER_LANGUAGES = {1: ({'code': 'en-us'}, {'code': 'de'},)}
 | 
			
		||||
PARLER_LANGUAGES = {SITE_ID: ({'code': 'en-us'}, {'code': 'de'},)}
 | 
			
		||||
AUTH_USER_MODEL = 'membership.CustomUser'
 | 
			
		||||
 | 
			
		||||
# PAYMENT
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,8 +18,8 @@ import debug_toolbar
 | 
			
		|||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    url(r'^index.html$', LandingView.as_view()),
 | 
			
		||||
    url(r'^open_api/', include('opennebula_api.urls',
 | 
			
		||||
                               namespace='opennebula_api')),
 | 
			
		||||
    url(r'^open_api/',
 | 
			
		||||
        include('opennebula_api.urls', namespace='opennebula_api')),
 | 
			
		||||
    url(r'^railshosting/', RailsHostingView.as_view(),
 | 
			
		||||
        name="rails.hosting"),
 | 
			
		||||
    url(r'^nodehosting/', NodeJSHostingView.as_view(),
 | 
			
		||||
| 
						 | 
				
			
			@ -28,8 +28,7 @@ urlpatterns = [
 | 
			
		|||
        name="django.hosting"),
 | 
			
		||||
    url(r'^nosystemd/', include('nosystemd.urls', namespace="nosystemd")),
 | 
			
		||||
    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),
 | 
			
		||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 | 
			
		||||
 | 
			
		||||
urlpatterns += i18n_patterns(
 | 
			
		||||
| 
						 | 
				
			
			@ -45,29 +44,22 @@ urlpatterns += i18n_patterns(
 | 
			
		|||
    url(r'^admin/', include(admin.site.urls)),
 | 
			
		||||
    url(r'^datacenterlight/',
 | 
			
		||||
        include('datacenterlight.urls', namespace="datacenterlight")),
 | 
			
		||||
    url(r'^hosting/', RedirectView.as_view(
 | 
			
		||||
        url=reverse_lazy('hosting:login')), name='redirect_hosting_login'),
 | 
			
		||||
    url(r'^hosting/', RedirectView.as_view(url=reverse_lazy('hosting:login')),
 | 
			
		||||
        name='redirect_hosting_login'),
 | 
			
		||||
    url(r'^alplora/', include('alplora.urls', namespace="alplora")),
 | 
			
		||||
    url(r'^membership/', include(membership_urls)),
 | 
			
		||||
    url(r'^digitalglarus/', include('digitalglarus.urls',
 | 
			
		||||
                                    namespace="digitalglarus")),
 | 
			
		||||
    url(r'^cms/blog/',
 | 
			
		||||
        include('ungleich.urls', namespace='ungleich')),
 | 
			
		||||
    url(
 | 
			
		||||
        r'^blog/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>\w[-\w]*)/$',
 | 
			
		||||
    url(r'^digitalglarus/',
 | 
			
		||||
        include('digitalglarus.urls', namespace="digitalglarus")),
 | 
			
		||||
    url(r'^cms/blog/', include('ungleich.urls', namespace='ungleich')),
 | 
			
		||||
    url(r'^blog/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>\w[-\w]*)/$',
 | 
			
		||||
        RedirectView.as_view(pattern_name='ungleich:post-detail')),
 | 
			
		||||
    url(r'^blog/$', RedirectView.as_view(
 | 
			
		||||
                url=reverse_lazy('ungleich:post-list')
 | 
			
		||||
            ), name='blog_list_view'
 | 
			
		||||
        ),
 | 
			
		||||
    url(r'^blog/$',
 | 
			
		||||
        RedirectView.as_view(url=reverse_lazy('ungleich:post-list')), name='blog_list_view'),
 | 
			
		||||
    url(r'^cms/', include('cms.urls')),
 | 
			
		||||
    url(r'^blog/', include('djangocms_blog.urls', namespace='djangocms_blog')),
 | 
			
		||||
    url(r'^$', RedirectView.as_view(url='/cms') if REDIRECT_TO_CMS
 | 
			
		||||
        else LandingView.as_view()),
 | 
			
		||||
    url(r'^',
 | 
			
		||||
        include('ungleich_page.urls',
 | 
			
		||||
                namespace='ungleich_page'),
 | 
			
		||||
        name='ungleich_page'),
 | 
			
		||||
    url(r'^blog/', include('djangocms_blog.urls', namespace='djangocms_blog')),
 | 
			
		||||
    url(r'^', include('ungleich_page.urls', namespace='ungleich_page')),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
urlpatterns += [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										23
									
								
								hosting/migrations/0044_hostingorder_vm_pricing.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								hosting/migrations/0044_hostingorder_vm_pricing.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
# Generated by Django 1.9.4 on 2018-04-16 00:22
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('datacenterlight', '0019_auto_20180415_2236'),
 | 
			
		||||
        ('hosting', '0043_vmdetail'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='hostingorder',
 | 
			
		||||
            name='vm_pricing',
 | 
			
		||||
            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='datacenterlight.VMPricing'),
 | 
			
		||||
            preserve_default=False,
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			@ -6,6 +6,8 @@ from django.db import models
 | 
			
		|||
from django.utils import timezone
 | 
			
		||||
from django.utils.functional import cached_property
 | 
			
		||||
from Crypto.PublicKey import RSA
 | 
			
		||||
 | 
			
		||||
from datacenterlight.models import VMPricing
 | 
			
		||||
from membership.models import StripeCustomer, CustomUser
 | 
			
		||||
from utils.models import BillingAddress
 | 
			
		||||
from utils.mixins import AssignPermissionsMixin
 | 
			
		||||
| 
						 | 
				
			
			@ -53,6 +55,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
 | 
			
		|||
    stripe_charge_id = models.CharField(max_length=100, null=True)
 | 
			
		||||
    price = models.FloatField()
 | 
			
		||||
    subscription_id = models.CharField(max_length=100, null=True)
 | 
			
		||||
    vm_pricing = models.ForeignKey(VMPricing)
 | 
			
		||||
 | 
			
		||||
    permissions = ('view_hostingorder',)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -70,12 +73,13 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
 | 
			
		|||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def create(cls, price=None, vm_id=None, customer=None,
 | 
			
		||||
               billing_address=None):
 | 
			
		||||
               billing_address=None, vm_pricing=None):
 | 
			
		||||
        instance = cls.objects.create(
 | 
			
		||||
            price=price,
 | 
			
		||||
            vm_id=vm_id,
 | 
			
		||||
            customer=customer,
 | 
			
		||||
            billing_address=billing_address
 | 
			
		||||
            billing_address=billing_address,
 | 
			
		||||
            vm_pricing=vm_pricing
 | 
			
		||||
        )
 | 
			
		||||
        instance.assign_permissions(customer.user)
 | 
			
		||||
        return instance
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -127,9 +127,19 @@
 | 
			
		|||
                            <span>{% trans "Disk space" %}: </span>
 | 
			
		||||
                            <span class="pull-right">{{vm.disk_size}} GB</span>
 | 
			
		||||
                        </p>
 | 
			
		||||
                        {% if vm.vat > 0 %}
 | 
			
		||||
                            <p>
 | 
			
		||||
                            <span>{% trans "Total" %}</span>
 | 
			
		||||
                            <span class="pull-right">{{vm.price|intcomma}} CHF</span>
 | 
			
		||||
                                <strong>{% trans "Subtotal" %}: </strong>
 | 
			
		||||
                                <span class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</span>
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <p>
 | 
			
		||||
                                <span>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%): </span>
 | 
			
		||||
                                <span class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</span>
 | 
			
		||||
                            </p>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        <p>
 | 
			
		||||
                            <strong>{% trans "Total" %}</strong>
 | 
			
		||||
                            <span class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %} CHF</span>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,7 @@
 | 
			
		|||
                <tr>
 | 
			
		||||
                    <td class="xs-td-inline" data-header="{% trans 'Order Nr.' %}">{{ order.id }}</td>
 | 
			
		||||
                    <td class="xs-td-bighalf" data-header="{% trans 'Date' %}">{{ order.created_at | date:"M d, Y H:i" }}</td>
 | 
			
		||||
                    <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|intcomma }}</td>
 | 
			
		||||
                    <td class="xs-td-smallhalf" data-header="{% trans 'Amount' %}">{{ order.price|floatformat:2|intcomma }}</td>
 | 
			
		||||
                    <td class="text-right last-td">
 | 
			
		||||
                        <a class="btn btn-order-detail" href="{% url 'hosting:orders' order.pk %}">{% trans 'See Invoice' %}</a>
 | 
			
		||||
                    </td>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,7 @@
 | 
			
		|||
				<h2 class="vm-detail-title">{% trans "Billing" %} <img src="{% static 'hosting/img/billing.svg' %}" class="un-icon"></h2>
 | 
			
		||||
				<div class="vm-vmid">
 | 
			
		||||
					<div class="vm-item-subtitle">{% trans "Current Pricing" %}</div>
 | 
			
		||||
					<div class="vm-item-lg">{{virtual_machine.price|floatformat|intcomma}} CHF/{% trans "Month" %}</div>
 | 
			
		||||
					<div class="vm-item-lg">{{order.price|floatformat:2|intcomma}} CHF/{% trans "Month" %}</div>
 | 
			
		||||
					<a class="btn btn-vm-invoice" href="{% url 'hosting:orders' order.pk %}">{% trans "See Invoice" %}</a>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,7 +43,7 @@ from utils.forms import (
 | 
			
		|||
    BillingAddressForm, PasswordResetRequestForm, UserBillingAddressForm,
 | 
			
		||||
    ResendActivationEmailForm
 | 
			
		||||
)
 | 
			
		||||
from utils.hosting_utils import get_vm_price
 | 
			
		||||
from utils.hosting_utils import get_vm_price, get_vm_price_with_vat
 | 
			
		||||
from utils.mailer import BaseEmail
 | 
			
		||||
from utils.stripe_utils import StripeUtils
 | 
			
		||||
from utils.tasks import send_plain_email_task
 | 
			
		||||
| 
						 | 
				
			
			@ -750,11 +750,17 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
 | 
			
		|||
                context['vm'] = vm_detail.__dict__
 | 
			
		||||
                context['vm']['name'] = '{}-{}'.format(
 | 
			
		||||
                    context['vm']['configuration'], context['vm']['vm_id'])
 | 
			
		||||
                context['vm']['price'] = get_vm_price(
 | 
			
		||||
                price, vat, vat_percent = get_vm_price_with_vat(
 | 
			
		||||
                    cpu=context['vm']['cores'],
 | 
			
		||||
                    disk_size=context['vm']['disk_size'],
 | 
			
		||||
                    memory=context['vm']['memory']
 | 
			
		||||
                    ssd_size=context['vm']['disk_size'],
 | 
			
		||||
                    memory=context['vm']['memory'],
 | 
			
		||||
                    pricing_name=(obj.vm_pricing.name
 | 
			
		||||
                                  if obj.vm_pricing else 'default')
 | 
			
		||||
                )
 | 
			
		||||
                context['vm']['vat'] = vat
 | 
			
		||||
                context['vm']['price'] = price
 | 
			
		||||
                context['vm']['vat_percent'] = vat_percent
 | 
			
		||||
                context['vm']['total_price'] = price + vat
 | 
			
		||||
                context['subscription_end_date'] = vm_detail.end_date()
 | 
			
		||||
            except VMDetail.DoesNotExist:
 | 
			
		||||
                try:
 | 
			
		||||
| 
						 | 
				
			
			@ -763,6 +769,17 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
 | 
			
		|||
                    )
 | 
			
		||||
                    vm = manager.get_vm(obj.vm_id)
 | 
			
		||||
                    context['vm'] = VirtualMachineSerializer(vm).data
 | 
			
		||||
                    price, vat, vat_percent = get_vm_price_with_vat(
 | 
			
		||||
                        cpu=context['vm']['cores'],
 | 
			
		||||
                        ssd_size=context['vm']['disk_size'],
 | 
			
		||||
                        memory=context['vm']['memory'],
 | 
			
		||||
                        pricing_name=(obj.vm_pricing.name
 | 
			
		||||
                                      if obj.vm_pricing else 'default')
 | 
			
		||||
                    )
 | 
			
		||||
                    context['vm']['vat'] = vat
 | 
			
		||||
                    context['vm']['price'] = price
 | 
			
		||||
                    context['vm']['vat_percent'] = vat_percent
 | 
			
		||||
                    context['vm']['total_price'] = price + vat
 | 
			
		||||
                except WrongIdError:
 | 
			
		||||
                    messages.error(
 | 
			
		||||
                        self.request,
 | 
			
		||||
| 
						 | 
				
			
			@ -1094,7 +1111,8 @@ class VirtualMachineView(LoginRequiredMixin, View):
 | 
			
		|||
            context = {
 | 
			
		||||
                'virtual_machine': serializer.data,
 | 
			
		||||
                'order': HostingOrder.objects.get(
 | 
			
		||||
                    vm_id=serializer.data['vm_id'])
 | 
			
		||||
                    vm_id=serializer.data['vm_id']
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        except Exception as ex:
 | 
			
		||||
            logger.debug("Exception generated {}".format(str(ex)))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,10 @@
 | 
			
		|||
    <!-- Collect the nav links, forms, and other content for toggling -->
 | 
			
		||||
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
 | 
			
		||||
      <ul class="nav navbar-nav navbar-right">
 | 
			
		||||
        <li>
 | 
			
		||||
          <a href="{% url 'djangocms_blog:posts-latest' %}">Ungleich Blog</a>
 | 
			
		||||
        </li>
 | 
			
		||||
        {% comment %}
 | 
			
		||||
        	{% for child in children %}
 | 
			
		||||
          	<li class="child{% if child.selected %} selected{% endif %}{% if child.ancestor %} ancestor{% endif %}{% if child.sibling %} sibling{% endif %}{% if child.descendant %} descendant{% endif %}">
 | 
			
		||||
          	  <a href="{{ child.attr.redirect_url|default:child.get_absolute_url }}">{{ child.get_menu_title }}</a>
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +32,7 @@
 | 
			
		|||
          	  {% endif %}
 | 
			
		||||
          	</li>
 | 
			
		||||
        	{% endfor %}
 | 
			
		||||
        {% endcomment %}
 | 
			
		||||
      </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- /.navbar-collapse -->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,8 @@
 | 
			
		|||
import decimal
 | 
			
		||||
import logging
 | 
			
		||||
from oca.pool import WrongIdError
 | 
			
		||||
 | 
			
		||||
from datacenterlight.models import VMPricing
 | 
			
		||||
from hosting.models import UserHostingKey, VMDetail
 | 
			
		||||
from opennebula_api.serializers import VirtualMachineSerializer
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -49,14 +51,74 @@ def get_or_create_vm_detail(user, manager, vm_id):
 | 
			
		|||
    return vm_detail_obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_vm_price(cpu, memory, disk_size):
 | 
			
		||||
def get_vm_price(cpu, memory, disk_size, hdd_size=0, pricing_name='default'):
 | 
			
		||||
    """
 | 
			
		||||
    A helper function that computes price of a VM from given cpu, ram and
 | 
			
		||||
    ssd parameters
 | 
			
		||||
 | 
			
		||||
    :param cpu: Number of cores of the VM
 | 
			
		||||
    :param memory: RAM of the VM
 | 
			
		||||
    :param disk_size: Disk space of the VM
 | 
			
		||||
    :param disk_size: Disk space of the VM (SSD)
 | 
			
		||||
    :param hdd_size: The HDD size
 | 
			
		||||
    :param pricing_name: The pricing name to be used
 | 
			
		||||
    :return: The price of the VM
 | 
			
		||||
    """
 | 
			
		||||
    return (cpu * 5) + (memory * 2) + (disk_size * 0.6)
 | 
			
		||||
    try:
 | 
			
		||||
        pricing = VMPricing.objects.get(name=pricing_name)
 | 
			
		||||
    except Exception as ex:
 | 
			
		||||
        logger.error(
 | 
			
		||||
            "Error getting VMPricing object for {pricing_name}."
 | 
			
		||||
            "Details: {details}".format(
 | 
			
		||||
                pricing_name=pricing_name, details=str(ex)
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        return None
 | 
			
		||||
    price = ((decimal.Decimal(cpu) * pricing.cores_unit_price) +
 | 
			
		||||
             (decimal.Decimal(memory) * pricing.ram_unit_price) +
 | 
			
		||||
             (decimal.Decimal(disk_size) * pricing.ssd_unit_price) +
 | 
			
		||||
             (decimal.Decimal(hdd_size) * pricing.hdd_unit_price))
 | 
			
		||||
    cents = decimal.Decimal('.01')
 | 
			
		||||
    price = price.quantize(cents, decimal.ROUND_HALF_UP)
 | 
			
		||||
    return float(price)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0,
 | 
			
		||||
                          pricing_name='default'):
 | 
			
		||||
    """
 | 
			
		||||
    A helper function that computes price of a VM from given cpu, ram and
 | 
			
		||||
    ssd, hdd and the pricing parameters
 | 
			
		||||
 | 
			
		||||
    :param cpu: Number of cores of the VM
 | 
			
		||||
    :param memory: RAM of the VM
 | 
			
		||||
    :param ssd_size: Disk space of the VM (SSD)
 | 
			
		||||
    :param hdd_size: The HDD size
 | 
			
		||||
    :param pricing_name: The pricing name to be used
 | 
			
		||||
    :return: The a tuple containing the price of the VM, the VAT and the
 | 
			
		||||
             VAT percentage
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        pricing = VMPricing.objects.get(name=pricing_name)
 | 
			
		||||
    except Exception as ex:
 | 
			
		||||
        logger.error(
 | 
			
		||||
            "Error getting VMPricing object for {pricing_name}."
 | 
			
		||||
            "Details: {details}".format(
 | 
			
		||||
                pricing_name=pricing_name, details=str(ex)
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    price = ((decimal.Decimal(cpu) * pricing.cores_unit_price) +
 | 
			
		||||
             (decimal.Decimal(memory) * pricing.ram_unit_price) +
 | 
			
		||||
             (decimal.Decimal(ssd_size) * pricing.ssd_unit_price) +
 | 
			
		||||
             (decimal.Decimal(hdd_size) * pricing.hdd_unit_price))
 | 
			
		||||
    if pricing.vat_inclusive:
 | 
			
		||||
        vat = decimal.Decimal(0)
 | 
			
		||||
        vat_percent = decimal.Decimal(0)
 | 
			
		||||
    else:
 | 
			
		||||
        vat = price * pricing.vat_percentage * decimal.Decimal(0.01)
 | 
			
		||||
        vat_percent = pricing.vat_percentage
 | 
			
		||||
 | 
			
		||||
    cents = decimal.Decimal('.01')
 | 
			
		||||
    price = price.quantize(cents, decimal.ROUND_HALF_UP)
 | 
			
		||||
    vat = vat.quantize(cents, decimal.ROUND_HALF_UP)
 | 
			
		||||
    return float(price), float(vat), float(vat_percent)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue