diff --git a/.gitignore b/.gitignore index e09fef54..1b2b4d16 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,5 @@ secret-key /utils/optimize/ # to keep empty dirs -!.gitkeep \ No newline at end of file +!.gitkeep +*.orig diff --git a/.travis.yml b/.travis.yml index 6a3cca25..3a3d7027 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ env: install: "pip install -r requirements.txt" script: - flake8 +- python manage.py compilemessages - python manage.py test -v 3 # - coverage run --source='.' manage.py test dynamicweb -v 3 # - coverage report diff --git a/Changelog b/Changelog index aa8df651..a9032757 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,88 @@ +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) + * #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) + * #5473: Ping a VM before saving ssh key of the user (PR #655) +2.1: 2018-08-21 + * Bugfix: Increase CC brand name fields from 10 to 128 characters (PR #654) +2.0.5: 2018-08-08 + * Fix IPv6 VM name in the billing invoice +2.0.4: 2018-08-07 + * Add RSS feed link to the footer of the blog template (PR #651) + * #5308: [ipv6only] Fix - when creating a VM, the name begins with v6only (PR #649) + * #5293: Use `terminate-hard` action instead of `terminate` in the opennebula call to terminate a vm (PR #650) +2.0.3: 2018-07-18 + * Remove unused /comic url (PR #644) + * #5126: Allow dynamicweb sites to be iframed on other by setting `X_FRAME_OPTIONS_ALLOW_FROM_URI` (PR #645) +2.0.2: 2018-07-14 + * bugfix: [blog] Add missing content block in the blog_ungleich.html template file +2.0.1: 2018-07-14 + * bugfix: [blog] Enable content/structure mode in blog page +2.0: 2018-07-07 + * #3747: [dcl,hosting] Add multiple cards support (PR #530) + * #3934: [dcl,hosting] Create HostingOrder outside celery task and add and associate OrderDetail with HostingOrder (PR #624) + * #4890: [hosting] Manage SSH keys using IPv6 of the VM (PR #640) + * bugfix: Fix flake8 error that was ignored in release 1.9.1 +1.9.1: 2018-06-24 + * #4799: [dcl] Show selected vm templates only in calculator (PR #638) + * #4847: [comic] Add google analytics code for comic.ungleich.ch (PR #639) + * feature: add vm_type option to vm_template and dcl calculator to distinguish between public and ipv6only templates (PR #635) +1.9: 2018-05-16 + * #4559: [cms] enable discount on cms calculator +1.8: 2018-05-01 + * #4527: [hosting] cms calculator on non-cms pages for the hosting app + * bgfix: [dcl] navbar dropdown target fix + * bgfix: [hosting] login/signup pages footer link fix +1.7.2: 2018-04-30 + * bgfix: [cms] add favicon extension to ungleich cms pages + * #4474: [cms] reduce heading slider side padding +1.7.1: 2018-04-21 + * #4481: [blog] fix de blog pages 500 error + * #4370: [comic] new url /comic to show only comic blogs +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 + * bgfix: [hosting] fix broken footer links + * bgfix: [dcl] remove ghost migrations from squashed migration + * bgfix: [cms] redirect multi-tenant urls to /cms also +1.6.4: 2018-04-06 + * #4362: [cms] Fix the need of dummy home page for different CMS-based sites +1.6.3: 2018-04-05 + * #4377: [cms] header btn external link fix + * #4378: [dcl cms] update CMS Integration to have different content for different domains +1.6.2: 2018-04-01 + * bgfix: [dcl] Fix user activation email style; add/correct some DE text + * #4373: [dcl] update footer menu for pw reset/login/signup/activation request pages +1.6.1: 2018-03-28 + * bgfix: fix header slider interval issue + * #4315: [cms] navbar consistency from cms page to static page + * #4313: [hosting] footer style fix +1.6: 2018-03-25 + * #4266: [dcl cms] add promotional section plugin + * #3842: [dcl, hosting] change number formatting for all the numbers from german to english locale +1.5.5: 2018-03-22 + * #4278: [dcl cms] edit options for cms navbar and header plugins + * bgfix: [dcl cms] fix link plugin issues and section image alignment +1.5.4: 2018-03-17 + * bgfix: [dcl cms] update DCLNavbarPlugin to allow change of brand logo and url +1.5.3: 2018-03-16 + * #4262: [dcl] Bugfix for incorrect template name +1.5.2: 2018-03-14 + * [devuan, ipv6] Add google analytics code for devuanhosting.com, ipv6onlyhosting.{com,net} + * #4246: [dcl cms] Enable full width options for DCL plugins + * #4247: [dcl cms] Fix alignment issues with the "plain heading" option +1.5.1: 2018-03-11 + * bgfix: [dcl cms] Remove datacenterlight_content placeholder conf so that we can create a cms page without calculator 1.5: 2018-03-09 * #3554: [dcl] Remove some more beta access resources (some were left in the earlier release) * #3452: [hosting] Back button management and cache control for hosting views @@ -47,7 +132,7 @@ * [cms] Introduce UngleichHeaderBackgroundImageAndTextSliderPlugin that allows to have scrolling images and texts * [cms] Remove
tag for ungleich cms customer item template
1.2.12: 2017-12-09
- * #3594: [digitalglarus] Remove white scroll bar on the right in mobile
+ * #3594: [digitalglarus] Remove white scroll bar on the right in mobile
* #3905: [ungleich] Update ungleich.ch header into a slider
* #3968: [ungleich] Fix navbar logo alignment
* [all] Enable logging custom modules
diff --git a/datacenterlight/admin.py b/datacenterlight/admin.py
new file mode 100644
index 00000000..5a1fc8a2
--- /dev/null
+++ b/datacenterlight/admin.py
@@ -0,0 +1,19 @@
+from django.contrib import admin
+from cms.admin.placeholderadmin import PlaceholderAdminMixin
+from cms.extensions import PageExtensionAdmin
+from .cms_models import CMSIntegration, CMSFaviconExtension
+from .models import VMPricing, VMTemplate
+
+
+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)
+admin.site.register(VMTemplate)
diff --git a/datacenterlight/cms_models.py b/datacenterlight/cms_models.py
index 20e39fde..62a7b312 100644
--- a/datacenterlight/cms_models.py
+++ b/datacenterlight/cms_models.py
@@ -1,12 +1,56 @@
-from djangocms_text_ckeditor.fields import HTMLField
+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 import forms
+from django.conf import settings
+from django.contrib.postgres.fields import ArrayField
+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
-# Models for CMS Plugins
+from datacenterlight.models import VMPricing, VMTemplate
+class CMSIntegration(models.Model):
+ name = models.CharField(
+ max_length=100, default='default',
+ help_text=(
+ 'A unique name for the Integration. This name will be used to '
+ 'fetch the Integration into pages'
+ )
+ )
+ footer_placeholder = PlaceholderField(
+ 'datacenterlight_footer', related_name='dcl-footer-placeholder+'
+ )
+ navbar_placeholder = PlaceholderField(
+ 'datacenterlight_navbar', related_name='dcl-navbar-placeholder+'
+ )
+ calculator_placeholder = PlaceholderField(
+ 'datacenterlight_calculator',
+ related_name='dcl-calculator-placeholder+'
+ )
+ domain = models.ForeignKey(Site, null=True, blank=True)
+
+ class Meta:
+ unique_together = ('name', 'domain')
+
+ def __str__(self):
+ return self.name
+
+
+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,
@@ -120,6 +164,32 @@ class DCLLinkPluginModel(CMSPlugin):
)
+class DCLNavbarPluginModel(CMSPlugin):
+ logo_light = FilerImageField(
+ on_delete=models.CASCADE, null=True, blank=True,
+ help_text='Logo to be used on transparent navbar',
+ related_name="dcl_navbar_logo_light",
+ )
+ logo_dark = FilerImageField(
+ on_delete=models.CASCADE, null=True, blank=True,
+ help_text='Logo to be used on white navbar',
+ related_name="dcl_navbar_logo_dark",
+ )
+ logo_url = models.URLField(max_length=300, null=True, blank=True)
+ language_dropdown = models.BooleanField(
+ default=True,
+ help_text='Select to include the language selection dropdown.'
+ )
+
+ def get_logo_dark(self):
+ # used only if atleast one logo exists
+ return self.logo_dark.url if self.logo_dark else self.logo_light.url
+
+ def get_logo_light(self):
+ # used only if atleast one logo exists
+ return self.logo_light.url if self.logo_light else self.logo_dark.url
+
+
class DCLNavbarDropdownPluginModel(CMSPlugin):
target = models.CharField(
max_length=100, null=True, blank=True,
@@ -134,7 +204,7 @@ class DCLNavbarDropdownPluginModel(CMSPlugin):
class DCLContactPluginModel(CMSPlugin):
heading = models.CharField(max_length=100, default="Contact", blank=True)
organization_name = models.CharField(
- max_length=100, default="ungleich GmbH", blank=True
+ max_length=100, default="ungleich glarus ag", blank=True
)
email = models.EmailField(max_length=200, default="info@ungleich.ch")
address = models.CharField(
@@ -150,7 +220,7 @@ class DCLContactPluginModel(CMSPlugin):
class DCLFooterPluginModel(CMSPlugin):
copyright_label = models.CharField(
- max_length=100, default='ungleich GmbH', blank=True,
+ max_length=100, default='ungleich glarus ag', blank=True,
help_text='Name of the company alongside the copyright year'
)
@@ -178,3 +248,105 @@ class DCLSectionImagePluginModel(CMSPlugin):
max_length=100, null=True, blank=True,
help_text='Optional caption for the image.'
)
+
+
+class DCLSectionPromoPluginModel(CMSPlugin):
+ background_image = FilerImageField(
+ on_delete=models.CASCADE, null=True, blank=True,
+ help_text=('Optional background image for the Promo Section'),
+ related_name="dcl_section_promo_promo",
+ )
+ heading = models.CharField(
+ blank=True, null=True, max_length=100,
+ help_text='An optional heading for the Promo Section',
+ )
+ subheading = models.CharField(
+ blank=True, null=True, max_length=200,
+ help_text='An optional subheading for the Promo Section',
+ )
+ content = HTMLField()
+ html_id = models.SlugField(
+ blank=True, null=True,
+ help_text=(
+ 'An optional html id for the Section. Required to set as target '
+ 'of a link on page'
+ )
+ )
+ plain_heading = models.BooleanField(
+ default=False,
+ help_text='Select to keep the heading style simpler.'
+ )
+ text_center = models.BooleanField(
+ default=False,
+ help_text='Select to center align content on small screens.'
+ )
+
+ def __str__(self):
+ return '#' + self.html_id if self.html_id else str(self.pk)
+
+ def get_extra_classes(self):
+ extra_classes = ''
+ if self.text_center:
+ extra_classes += ' text-center'
+ if self.plain_heading:
+ extra_classes += ' promo-section-plain'
+ if self.background_image:
+ extra_classes += ' promo-with-bg'
+ return extra_classes
+
+
+class MultipleChoiceArrayField(ArrayField):
+ """
+ A field that allows us to store an array of choices.
+ Uses Django's Postgres ArrayField
+ and a MultipleChoiceField for its formfield.
+ """
+ VMTemplateChoices = []
+ if settings.OPENNEBULA_DOMAIN != 'test_domain':
+ VMTemplateChoices = list(
+ (
+ str(obj.opennebula_vm_template_id),
+ (obj.name + ' - ' + VMTemplate.IPV6.title()
+ if obj.vm_type == VMTemplate.IPV6 else obj.name
+ )
+ )
+ for obj in VMTemplate.objects.all()
+ )
+
+ def formfield(self, **kwargs):
+ defaults = {
+ 'form_class': forms.MultipleChoiceField,
+ 'choices': self.VMTemplateChoices,
+ }
+ defaults.update(kwargs)
+ # Skip our parent's formfield implementation completely as we don't
+ # care for it.
+ # pylint:disable=bad-super-call
+ return super(ArrayField, self).formfield(**defaults)
+
+
+class DCLCalculatorPluginModel(CMSPlugin):
+ pricing = models.ForeignKey(
+ VMPricing,
+ related_name="dcl_custom_pricing_vm_pricing",
+ help_text='Choose a pricing that will be associated with this '
+ 'Calculator'
+ )
+ vm_type = models.CharField(
+ max_length=50, choices=VMTemplate.VM_TYPE_CHOICES,
+ default=VMTemplate.PUBLIC
+ )
+ vm_templates_to_show = MultipleChoiceArrayField(
+ base_field=models.CharField(
+ blank=True,
+ max_length=256,
+ ),
+ default=list,
+ blank=True,
+ help_text="Recommended: If you wish to show all templates of the "
+ "corresponding VM Type (public/ipv6only), please do not "
+ "select any of the items in the above field. "
+ "This will allow any new template(s) added "
+ "in the backend to be automatically listed in this "
+ "calculator instance."
+ )
diff --git a/datacenterlight/cms_plugins.py b/datacenterlight/cms_plugins.py
index 70ecfaa2..95a496d8 100644
--- a/datacenterlight/cms_plugins.py
+++ b/datacenterlight/cms_plugins.py
@@ -1,12 +1,12 @@
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
-from cms.models.pluginmodel import CMSPlugin
from .cms_models import (
DCLBannerItemPluginModel, DCLBannerListPluginModel, DCLContactPluginModel,
DCLFooterPluginModel, DCLLinkPluginModel, DCLNavbarDropdownPluginModel,
DCLSectionIconPluginModel, DCLSectionImagePluginModel,
- DCLSectionPluginModel,
+ DCLSectionPluginModel, DCLNavbarPluginModel,
+ DCLSectionPromoPluginModel, DCLCalculatorPluginModel
)
from .models import VMTemplate
@@ -19,7 +19,31 @@ class DCLSectionPlugin(CMSPluginBase):
render_template = "datacenterlight/cms/section.html"
cache = False
allow_children = True
- child_classes = ['DCLSectionIconPlugin', 'DCLSectionImagePlugin']
+ child_classes = [
+ 'DCLSectionIconPlugin', 'DCLSectionImagePlugin',
+ 'DCLSectionPromoPlugin', 'UngleichHTMLPlugin', 'DCLCalculatorPlugin'
+ ]
+
+ def render(self, context, instance, placeholder):
+ context = super(DCLSectionPlugin, self).render(
+ context, instance, placeholder
+ )
+ context['children_to_side'] = []
+ context['children_to_content'] = []
+ context['children_calculator'] = []
+ if instance.child_plugin_instances is not None:
+ right_children = [
+ 'DCLSectionImagePluginModel',
+ 'DCLSectionIconPluginModel',
+ ]
+ for child in instance.child_plugin_instances:
+ if child.__class__.__name__ in right_children:
+ context['children_to_side'].append(child)
+ elif child.plugin_type == 'DCLCalculatorPlugin':
+ context['children_calculator'].append(child)
+ else:
+ context['children_to_content'].append(child)
+ return context
@plugin_pool.register_plugin
@@ -42,19 +66,37 @@ class DCLSectionImagePlugin(CMSPluginBase):
require_parent = True
+@plugin_pool.register_plugin
+class DCLSectionPromoPlugin(CMSPluginBase):
+ module = "Datacenterlight"
+ name = "DCL Section Promo Plugin"
+ model = DCLSectionPromoPluginModel
+ render_template = "datacenterlight/cms/section_promo.html"
+ cache = False
+
+
@plugin_pool.register_plugin
class DCLCalculatorPlugin(CMSPluginBase):
module = "Datacenterlight"
name = "DCL Calculator Plugin"
- model = DCLSectionPluginModel
+ model = DCLCalculatorPluginModel
render_template = "datacenterlight/cms/calculator.html"
cache = False
+ require_parent = True
def render(self, context, instance, placeholder):
context = super(DCLCalculatorPlugin, self).render(
context, instance, placeholder
)
- context['templates'] = VMTemplate.objects.all()
+ ids = instance.vm_templates_to_show
+ if ids:
+ context['templates'] = VMTemplate.objects.filter(
+ vm_type=instance.vm_type
+ ).filter(opennebula_vm_template_id__in=ids)
+ else:
+ context['templates'] = VMTemplate.objects.filter(
+ vm_type=instance.vm_type
+ )
return context
@@ -84,7 +126,7 @@ class DCLBannerItemPlugin(CMSPluginBase):
class DCLNavbarPlugin(CMSPluginBase):
module = "Datacenterlight"
name = "DCL Navbar Plugin"
- model = CMSPlugin
+ model = DCLNavbarPluginModel
render_template = "datacenterlight/cms/navbar.html"
cache = False
allow_children = True
diff --git a/datacenterlight/cms_toolbar.py b/datacenterlight/cms_toolbar.py
new file mode 100644
index 00000000..15a8cb4b
--- /dev/null
+++ b/datacenterlight/cms_toolbar.py
@@ -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
+ )
diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po
index 1b9bd116..1b66b640 100644
--- a/datacenterlight/locale/de/LC_MESSAGES/django.po
+++ b/datacenterlight/locale/de/LC_MESSAGES/django.po
@@ -8,65 +8,35 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-01-15 23:12+0000\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME
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."
-
-msgid "Thank you!"
-msgstr "Vielen Dank!"
-
msgid "Data Center Light Account Activation"
msgstr "Data Center Light Account Aktivierung"
@@ -136,7 +80,7 @@ msgid ""
"#4382c8; font-weight: 400;\">here."
msgstr ""
"Klicke here um deinen Data Center "
+"none; color: #4382c8; font-weight: 400;\">hier um deinen Data Center "
"Light Account zu aktivieren."
msgid ""
@@ -156,14 +100,26 @@ msgstr "Deine E-Mail-Adresse"
msgid "Password"
msgstr "Passwort"
-msgid "You can reset your password here"
-msgstr "Du kannst dein Passwort hier zurück setzen"
+#, python-format
+msgid ""
+"You can reset your password here."
+msgstr ""
+"Du kannst dein Passwort hier "
+"zurücksetzen."
+
+msgid "Your Data Center Light Team"
+msgstr "Dein Data Center Light Team"
msgid ""
"You can copy and paste the following link into the address bar of your "
"browser to activate your Data Center Light account."
msgstr "Kopiere den folgenden Link in die Adressleiste deines Browsers."
+msgid "You can reset your password here"
+msgstr "Du kannst dein Passwort hier zurücksetzen"
+
msgid "Welcome to Data Center Light!"
msgstr "Willkommen beim Data Center Light!"
@@ -178,23 +134,38 @@ msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!"
msgid "ORDER VM"
msgstr "VM BESTELLEN"
-msgid "Home"
-msgstr "Home"
-
-msgid "Highlights"
+msgid "VM hosting"
msgstr ""
-msgid "Scale out"
-msgstr "Skalierung"
+msgid "month"
+msgstr "Monat"
-msgid "Reliable and light"
-msgstr "Zuverlässig und leicht"
+msgid "VAT included"
+msgstr "MwSt. inklusive"
-msgid "Pricing"
-msgstr "Preise"
+msgid "You save"
+msgstr "Du sparst"
-msgid "Order VM"
-msgstr "VM bestellen"
+msgid "Hosted in Switzerland"
+msgstr "Standort: Schweiz"
+
+msgid "Please enter a value in range 1 - 48."
+msgstr "Bitte gib einen Wert von 1 bis 48 ein."
+
+msgid "Please enter a value in range 1 - 200."
+msgstr "Bitte gib einen Wert von 1 bis 200 ein."
+
+msgid "Please enter a value in range 10 - 2000."
+msgstr "Bitte gib einen Wert von 10 bis 2000 ein."
+
+msgid "GB Storage (SSD)"
+msgstr "GB Storage (SSD)"
+
+msgid "Continue"
+msgstr "Weiter"
+
+msgid "Home"
+msgstr "Home"
msgid "Contact"
msgstr "Kontakt"
@@ -202,24 +173,12 @@ msgstr "Kontakt"
msgid "Terms of Service"
msgstr "Nutzungsbedingungen"
-msgid "All Rights Reserved"
-msgstr "Alle Rechte vorbehalten"
-
-msgid "Toggle navigation"
-msgstr "Umschalten"
-
-msgid "Why Data Center Light?"
-msgstr "Warum Data Center Light?"
-
-msgid "Login"
-msgstr "Anmelden"
-
-msgid "Dashboard"
-msgstr ""
-
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!"
@@ -250,6 +209,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 "
@@ -259,6 +221,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 "
@@ -349,12 +314,32 @@ msgstr "Gesamt"
msgid "including VAT"
msgstr "inkl. Mehrwertsteuer"
+msgid "excluding VAT"
+msgstr "exkl. Mehrwertsteuer"
+
msgid "Month"
msgstr "Monat"
+msgid "Discount"
+msgstr "Rabatt"
+
+msgid "Will be applied at checkout"
+msgstr "wird an der Kasse angewendet"
+
msgid "Credit Card"
msgstr "Kreditkarte"
+msgid ""
+"Please select one of the cards that you used before or fill in your credit "
+"card information below. We are using Stripe for payment and do not store your information in our "
+"database."
+msgstr ""
+"Bitte wähle eine der zuvor genutzten Kreditkarten oder gib Deine "
+"Kreditkartendetails unten an. Die Bezahlung wird über "
+"Stripe abgewickelt. "
+"Wir speichern Deine Kreditkartendetails nicht in unserer Datenbank."
+
msgid ""
"Please fill in your credit card information below. We are using Stripe for payment and do not "
@@ -364,31 +349,23 @@ msgstr ""
"\"https://stripe.com\" target=\"_blank\">Stripe 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."
+msgid "Last"
+msgstr "Letzten"
-msgid "Card Number"
-msgstr "Kreditkartennummer"
+msgid "Type"
+msgstr "Typ"
-msgid "Expiry Date"
-msgstr "Ablaufdatum"
+msgid "SELECT"
+msgstr "AUSWÄHLEN"
-msgid "CVC"
-msgstr ""
+msgid "Add a new credit card"
+msgstr "Eine neue Kreditkarte hinzufügen"
-msgid "Card Type"
-msgstr "Kartentyp"
+msgid "NEW CARD"
+msgstr "NEUE KARTE"
-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."
+msgid "New Credit Card"
+msgstr "Neue Kreditkarte"
msgid "Processing"
msgstr "Weiter"
@@ -396,25 +373,63 @@ msgstr "Weiter"
msgid "Enter your credit card number"
msgstr "Deine Kreditkartennummer"
+#, python-format
+msgid "%(page_header_text)s"
+msgstr ""
+
+msgid "Date"
+msgstr "Datum"
+
+msgid "Billed to"
+msgstr "Rechnungsadresse"
+
+msgid "Payment method"
+msgstr "Bezahlmethode"
+
+msgid "ending in"
+msgstr "endend in"
+
+msgid "Order summary"
+msgstr "Bestellungsübersicht"
+
+msgid "Product"
+msgstr "Produkt"
+
+msgid "Subtotal"
+msgstr "Zwischensumme"
+
+msgid "VAT"
+msgstr "Mehrwertsteuer"
+
#, python-format
msgid ""
"By clicking \"Place order\" this plan will charge your credit card account "
-"with the fee of %(vm_price)sCHF/month"
+"with %(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"
-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."
+msgid "Processing..."
+msgstr "Abarbeitung..."
-msgid "as soon as possible!"
+msgid "Hold tight, we are processing your request"
+msgstr "Bitte warten - wir verarbeiten Deine Anfrage gerade"
+
+msgid "OK"
msgstr ""
+msgid "Close"
+msgstr ""
+
+msgid "Some problem encountered. Please try again later."
+msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
+
+msgid "Why Data Center Light?"
+msgstr "Warum Data Center Light?"
+
msgid "Tech Stack"
msgstr "Tech Stack"
@@ -500,6 +515,17 @@ 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 ""
+
+#, python-brace-format
+msgid "{user} does not have permission to access the card"
+msgstr "{user} hat keine Erlaubnis auf diese Karte zuzugreifen"
+
+msgid "An error occurred. Details: {}"
+msgstr "Ein Fehler ist aufgetreten. Details: {}"
+
msgid "Confirm Order"
msgstr "Bestellung Bestätigen"
@@ -513,6 +539,11 @@ msgstr ""
"Es ist ein Fehler bei der Zahlung betreten. Du wirst nach dem Schliessen vom "
"Popup zur Bezahlseite weitergeleitet."
+#, python-brace-format
+msgid "An error occurred while associating the card. Details: {details}"
+msgstr "Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: "
+"{details}"
+
msgid "Thank you for the order."
msgstr "Danke für Deine Bestellung."
@@ -523,36 +554,88 @@ msgstr ""
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
"auf sie zugreifen kannst."
+#~ 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."
+
+#~ msgid "Card Number"
+#~ msgstr "Kreditkartennummer"
+
+#~ msgid "Expiry Date"
+#~ msgstr "Ablaufdatum"
+
+#~ 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."
+
+#~ msgid "Pricing"
+#~ msgstr "Preise"
+
+#~ msgid "Order VM"
+#~ msgstr "VM bestellen"
+
+#~ msgid "Enter name"
+#~ msgstr "Name"
+
+#~ msgid "Enter email"
+#~ msgstr "E-Mail-Adresse"
+
+#~ msgid "Request Beta Access"
+#~ msgstr "Beantrage Beta-Zugang"
+
+#~ msgid "Request Sent"
+#~ msgstr "Anfrage verschickt"
+
+#~ msgid ""
+#~ "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"
+
+#~ 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!"
+
+#~ msgid ""
+#~ "Currently we are running our tests to make sure everything runs perfectly."
+#~ msgstr ""
+#~ "Momentan testen wir die Beta-Umgebung um sie für Ihren Gebrauch "
+#~ "sicherzustellen."
+
+#~ msgid ""
+#~ "In the meantime, we would like to ask you a little patience
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."
+
+#~ msgid "Thank you!"
+#~ msgstr "Vielen Dank!"
+
+#~ 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."
+
#~ msgid "Affordable VM hosting based in Switzerland"
#~ msgstr "Bezahlbares VM Hosting in der Schweiz"
-#~ msgid "Processing..."
-#~ msgstr "Abarbeitung..."
-
-#~ msgid "Hold tight, we are processing your request"
-#~ msgstr "Bitte warten - wir verbeiten Deine Anfrage gerade"
-
-#~ msgid "Some problem encountered. Please try again later."
-#~ msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
-
#~ msgid "Submit"
#~ msgstr "Absenden"
-#~ msgid "Date"
-#~ msgstr "Datum"
-
-#~ msgid "Billed To:"
-#~ msgstr "Rechnungsadresse"
-
-#~ msgid "Payment Method:"
-#~ msgstr "Bezahlmethode"
-
-#~ msgid "ending in"
-#~ msgstr "endend in"
-
-#~ msgid "Order summary"
-#~ msgstr "Bestellungsübersicht"
-
#~ msgid "We are cutting down the costs significantly!"
#~ msgstr "Wir sorgen dafür, dass die Kosten für Dich signifikant abnehmen"
diff --git a/datacenterlight/management/commands/cmsintegrate.py b/datacenterlight/management/commands/cmsintegrate.py
new file mode 100644
index 00000000..206248d2
--- /dev/null
+++ b/datacenterlight/management/commands/cmsintegrate.py
@@ -0,0 +1,21 @@
+from django.core.management.base import BaseCommand
+from datacenterlight.cms_models import CMSIntegration
+
+
+class Command(BaseCommand):
+ help = '''Creates cms integration objects for datacenterlight'''
+
+ def handle(self, *args, **options):
+ self.create_cms_integration()
+
+ def create_cms_integration(self, site=None):
+ obj, created = CMSIntegration.objects.get_or_create(
+ name='default', domain=site
+ )
+ domain_name = site.domain if site else 'All Sites'
+ if created:
+ print('created the default CMSIntegration object for', domain_name)
+ else:
+ print(
+ 'default CMSIntegration object already exists for', domain_name
+ )
diff --git a/datacenterlight/management/commands/create_default_vm_pricing.py b/datacenterlight/management/commands/create_default_vm_pricing.py
new file mode 100644
index 00000000..c1b36eea
--- /dev/null
+++ b/datacenterlight/management/commands/create_default_vm_pricing.py
@@ -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
+ )
+ )
diff --git a/datacenterlight/management/commands/fetchvmtemplates.py b/datacenterlight/management/commands/fetchvmtemplates.py
index 15b76fc1..89271dc4 100644
--- a/datacenterlight/management/commands/fetchvmtemplates.py
+++ b/datacenterlight/management/commands/fetchvmtemplates.py
@@ -7,18 +7,31 @@ logger = logging.getLogger(__name__)
class Command(BaseCommand):
- help = 'Fetches the VM templates from OpenNebula and populates the dcl VMTemplate model'
+ help = '''Fetches the VM templates from OpenNebula and populates the dcl
+ VMTemplate model'''
+
+ def get_templates(self, manager, prefix):
+ templates = manager.get_templates('%s-' % prefix)
+ dcl_vm_templates = []
+ for template in templates:
+ template_name = template.name.lstrip('%s-' % prefix)
+ template_id = template.id
+ dcl_vm_template = VMTemplate.create(
+ template_name, template_id, prefix
+ )
+ dcl_vm_templates.append(dcl_vm_template)
+ return dcl_vm_templates
def handle(self, *args, **options):
try:
manager = OpenNebulaManager()
- templates = manager.get_templates()
dcl_vm_templates = []
- for template in templates:
- template_name = template.name.strip('public-')
- template_id = template.id
- dcl_vm_template = VMTemplate.create(template_name, template_id)
- dcl_vm_templates.append(dcl_vm_template)
+ dcl_vm_templates.extend(
+ self.get_templates(manager, VMTemplate.PUBLIC)
+ )
+ dcl_vm_templates.extend(
+ self.get_templates(manager, VMTemplate.IPV6)
+ )
old_vm_templates = VMTemplate.objects.all()
old_vm_templates.delete()
@@ -26,4 +39,5 @@ class Command(BaseCommand):
for dcl_vm_template in dcl_vm_templates:
dcl_vm_template.save()
except Exception as e:
- logger.error('Error connecting to OpenNebula. Error Details: {err}'.format(err=str(e)))
+ logger.error('Error connecting to OpenNebula. Error Details: '
+ '{err}'.format(err=str(e)))
diff --git a/datacenterlight/migrations/0013_dclnavbarpluginmodel.py b/datacenterlight/migrations/0013_dclnavbarpluginmodel.py
new file mode 100644
index 00000000..47fa5e54
--- /dev/null
+++ b/datacenterlight/migrations/0013_dclnavbarpluginmodel.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-03-17 07:19
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import filer.fields.image
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0012_dclcalculatorpluginmodel'),
+ ('cms', '0014_auto_20160404_1908'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='DCLNavbarPluginModel',
+ 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')),
+ ('logo_url', models.URLField(blank=True, max_length=300, null=True)),
+ ('logo_dark', filer.fields.image.FilerImageField(blank=True, help_text='Logo to be used on white navbar', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='dcl_navbar_logo_dark', to='filer.Image')),
+ ('logo_light', filer.fields.image.FilerImageField(blank=True, help_text='Logo to be used on transparent navbar', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='dcl_navbar_logo_light', to='filer.Image')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=('cms.cmsplugin',),
+ ),
+ ]
diff --git a/datacenterlight/migrations/0014_dclnavbarpluginmodel_language_dropdown.py b/datacenterlight/migrations/0014_dclnavbarpluginmodel_language_dropdown.py
new file mode 100644
index 00000000..ba90af39
--- /dev/null
+++ b/datacenterlight/migrations/0014_dclnavbarpluginmodel_language_dropdown.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-03-19 20:46
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0013_dclnavbarpluginmodel'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='dclnavbarpluginmodel',
+ name='language_dropdown',
+ field=models.BooleanField(
+ default=True, help_text='Select to include the language selection dropdown.'),
+ ),
+ ]
diff --git a/datacenterlight/migrations/0014_dclsectionpromopluginmodel.py b/datacenterlight/migrations/0014_dclsectionpromopluginmodel.py
new file mode 100644
index 00000000..81e8d6f0
--- /dev/null
+++ b/datacenterlight/migrations/0014_dclsectionpromopluginmodel.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-03-21 19:09
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import djangocms_text_ckeditor.fields
+import filer.fields.image
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cms', '0014_auto_20160404_1908'),
+ ('datacenterlight', '0013_dclnavbarpluginmodel'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='DCLSectionPromoPluginModel',
+ 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')),
+ ('heading', models.CharField(
+ blank=True, help_text='An optional heading for the Promo Section', max_length=100, null=True)),
+ ('subheading', models.CharField(
+ blank=True, help_text='An optional subheading for the Promo Section', max_length=200, null=True)),
+ ('content', djangocms_text_ckeditor.fields.HTMLField()),
+ ('html_id', models.SlugField(
+ blank=True, help_text='An optional html id for the Section. Required to set as target of a link on page', null=True)),
+ ('plain_heading', models.BooleanField(default=False,
+ help_text='Select to keep the heading style simpler.')),
+ ('center_on_mobile', models.BooleanField(default=False,
+ help_text='Select to center align content on small screens.')),
+ ('background_image', filer.fields.image.FilerImageField(blank=True, help_text='Optional background image for the Promo Section',
+ null=True, on_delete=django.db.models.deletion.CASCADE, related_name='dcl_section_promo_promo', to='filer.Image')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=('cms.cmsplugin',),
+ ),
+ ]
diff --git a/datacenterlight/migrations/0015_auto_20180323_0011.py b/datacenterlight/migrations/0015_auto_20180323_0011.py
new file mode 100644
index 00000000..a46cb789
--- /dev/null
+++ b/datacenterlight/migrations/0015_auto_20180323_0011.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-03-22 19:22
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0014_dclsectionpromopluginmodel'),
+ ('datacenterlight', '0014_dclnavbarpluginmodel_language_dropdown'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='dclsectionpromopluginmodel',
+ old_name='center_on_mobile',
+ new_name='text_center',
+ ),
+ ]
diff --git a/datacenterlight/migrations/0016_cmsintegration.py b/datacenterlight/migrations/0016_cmsintegration.py
new file mode 100644
index 00000000..bdd1813a
--- /dev/null
+++ b/datacenterlight/migrations/0016_cmsintegration.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-03-27 15:31
+from __future__ import unicode_literals
+
+import cms.models.fields
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0015_auto_20180323_0011'),
+ ('cms', '0014_auto_20160404_1908'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CMSIntegration',
+ fields=[
+ ('id', models.AutoField(auto_created=True,
+ primary_key=True, serialize=False, verbose_name='ID')),
+ ('navbar_placeholder', cms.models.fields.PlaceholderField(editable=False, null=True,
+ on_delete=django.db.models.deletion.CASCADE, slotname='datacenterlight_navbar', to='cms.Placeholder')),
+ ('footer_placeholder', cms.models.fields.PlaceholderField(editable=False, null=True,
+ on_delete=django.db.models.deletion.CASCADE, slotname='datacenterlight_footer', to='cms.Placeholder')),
+ ('name', models.CharField(default='default',
+ help_text='A unique name for the Integration. This name will be used to fetch the Integration into pages', max_length=100, unique=True)),
+ ],
+ ),
+ ]
diff --git a/datacenterlight/migrations/0017_auto_20180329_0056.py b/datacenterlight/migrations/0017_auto_20180329_0056.py
new file mode 100644
index 00000000..136e6dbd
--- /dev/null
+++ b/datacenterlight/migrations/0017_auto_20180329_0056.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-03-28 19:26
+from __future__ import unicode_literals
+
+import cms.models.fields
+from django.db import migrations
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0016_cmsintegration'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='cmsintegration',
+ name='footer_placeholder',
+ field=cms.models.fields.PlaceholderField(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='dcl-footer-placeholder+', slotname='datacenterlight_footer', to='cms.Placeholder'),
+ ),
+ migrations.AlterField(
+ model_name='cmsintegration',
+ name='navbar_placeholder',
+ field=cms.models.fields.PlaceholderField(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='dcl-navbar-placeholder+', slotname='datacenterlight_navbar', to='cms.Placeholder'),
+ ),
+ ]
diff --git a/datacenterlight/migrations/0018_auto_20180403_1930.py b/datacenterlight/migrations/0018_auto_20180403_1930.py
new file mode 100644
index 00000000..a894c66b
--- /dev/null
+++ b/datacenterlight/migrations/0018_auto_20180403_1930.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-04-03 17:08
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0017_auto_20180329_0056'),
+ ('sites', '0002_alter_domain_unique'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='dclcontactpluginmodel',
+ name='organization_name',
+ field=models.CharField(blank=True, default='ungleich glarus ag', max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='dclfooterpluginmodel',
+ name='copyright_label',
+ field=models.CharField(blank=True, default='ungleich glarus ag', help_text='Name of the company alongside the copyright year', max_length=100),
+ ),
+ migrations.AddField(
+ model_name='cmsintegration',
+ name='domain',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'),
+ ),
+ migrations.AlterField(
+ model_name='cmsintegration',
+ name='name',
+ field=models.CharField(default='default', help_text='A unique name for the Integration. This name will be used to fetch the Integration into pages', max_length=100),
+ ),
+ migrations.AlterUniqueTogether(
+ name='cmsintegration',
+ unique_together=set([('name', 'domain')]),
+ ),
+ ]
diff --git a/datacenterlight/migrations/0019_auto_20180415_2236.py b/datacenterlight/migrations/0019_auto_20180415_2236.py
new file mode 100644
index 00000000..4b711a2b
--- /dev/null
+++ b/datacenterlight/migrations/0019_auto_20180415_2236.py
@@ -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'),
+ ),
+ ]
diff --git a/datacenterlight/migrations/0019_cmsfaviconextension.py b/datacenterlight/migrations/0019_cmsfaviconextension.py
new file mode 100644
index 00000000..7b350a70
--- /dev/null
+++ b/datacenterlight/migrations/0019_cmsfaviconextension.py
@@ -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,
+ },
+ ),
+ ]
diff --git a/datacenterlight/migrations/0020_merge.py b/datacenterlight/migrations/0020_merge.py
new file mode 100644
index 00000000..6bbe0086
--- /dev/null
+++ b/datacenterlight/migrations/0020_merge.py
@@ -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 = [
+ ]
diff --git a/datacenterlight/migrations/0021_cmsintegration_calculator_placeholder.py b/datacenterlight/migrations/0021_cmsintegration_calculator_placeholder.py
new file mode 100644
index 00000000..3ebbb469
--- /dev/null
+++ b/datacenterlight/migrations/0021_cmsintegration_calculator_placeholder.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-04-25 09:20
+from __future__ import unicode_literals
+
+import cms.models.fields
+from django.db import migrations
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0020_merge'),
+ ('cms', '0014_auto_20160404_1908'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='cmsintegration',
+ name='calculator_placeholder',
+ field=cms.models.fields.PlaceholderField(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE,
+ related_name='dcl-calculator-placeholder+', slotname='datacenterlight_calculator', to='cms.Placeholder'),
+ ),
+ migrations.RenameModel(
+ old_name='DCLCustomPricingModel',
+ new_name='DCLCalculatorPluginModel',
+ ),
+ ]
diff --git a/datacenterlight/migrations/0022_auto_20180506_1950.py b/datacenterlight/migrations/0022_auto_20180506_1950.py
new file mode 100644
index 00000000..a5554a58
--- /dev/null
+++ b/datacenterlight/migrations/0022_auto_20180506_1950.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-05-07 02:19
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0021_cmsintegration_calculator_placeholder'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='vmpricing',
+ name='discount_amount',
+ field=models.DecimalField(
+ decimal_places=2, default=0, max_digits=6),
+ ),
+ migrations.AddField(
+ model_name='vmpricing',
+ name='discount_name',
+ field=models.CharField(blank=True, max_length=255, null=True),
+ ),
+ ]
diff --git a/datacenterlight/migrations/0023_auto_20180524_0349.py b/datacenterlight/migrations/0023_auto_20180524_0349.py
new file mode 100644
index 00000000..f37d6634
--- /dev/null
+++ b/datacenterlight/migrations/0023_auto_20180524_0349.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-05-23 22:19
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0022_auto_20180506_1950'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='dclcalculatorpluginmodel',
+ name='vm_type',
+ field=models.CharField(choices=[('public', 'Public'), ('ipv6only', 'Ipv6Only')], default='public', max_length=50),
+ ),
+ migrations.AddField(
+ model_name='vmtemplate',
+ name='vm_type',
+ field=models.CharField(choices=[('public', 'Public'), ('ipv6only', 'Ipv6Only')], default='public', max_length=50),
+ ),
+ ]
diff --git a/datacenterlight/migrations/0024_dclcalculatorpluginmodel_vm_templates_to_show.py b/datacenterlight/migrations/0024_dclcalculatorpluginmodel_vm_templates_to_show.py
new file mode 100644
index 00000000..65bfce21
--- /dev/null
+++ b/datacenterlight/migrations/0024_dclcalculatorpluginmodel_vm_templates_to_show.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-06-24 08:23
+from __future__ import unicode_literals
+
+import datacenterlight.cms_models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0023_auto_20180524_0349'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='dclcalculatorpluginmodel',
+ name='vm_templates_to_show',
+ field=datacenterlight.cms_models.MultipleChoiceArrayField(base_field=models.CharField(blank=True, max_length=256), blank=True, default=list, help_text='Recommended: If you wish to show all templates of the corresponding VM Type (public/ipv6only), please do not select any of the items in the above field. This will allow any new template(s) added in the backend to be automatically listed in this calculator instance.', size=None),
+ ),
+ ]
diff --git a/datacenterlight/models.py b/datacenterlight/models.py
index 6fcf24a9..729bbdf9 100644
--- a/datacenterlight/models.py
+++ b/datacenterlight/models.py
@@ -1,17 +1,103 @@
+import logging
+
from django.db import models
+logger = logging.getLogger(__name__)
+
class VMTemplate(models.Model):
+ PUBLIC = 'public'
+ IPV6 = 'ipv6only'
+ VM_TYPE_CHOICES = (
+ (PUBLIC, PUBLIC.title()),
+ (IPV6, IPV6.title()),
+ )
name = models.CharField(max_length=50)
opennebula_vm_template_id = models.IntegerField()
+ vm_type = models.CharField(
+ max_length=50, choices=VM_TYPE_CHOICES, default=PUBLIC
+ )
+
+ def __str__(self):
+ return '%s - %s - %s' % (
+ self.opennebula_vm_template_id, self.vm_type, self.name
+ )
@classmethod
- def create(cls, name, opennebula_vm_template_id):
+ def create(cls, name, opennebula_vm_template_id, vm_type):
vm_template = cls(
- name=name, opennebula_vm_template_id=opennebula_vm_template_id)
+ name=name, opennebula_vm_template_id=opennebula_vm_template_id,
+ vm_type=vm_type
+ )
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
+ )
+ discount_name = models.CharField(max_length=255, null=True, blank=True)
+ discount_amount = models.DecimalField(
+ max_digits=6, decimal_places=2, default=0
+ )
+
+ def __str__(self):
+ display_str = 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',
+ ])
+ if self.discount_amount:
+ display_str = ' - '.join([
+ display_str,
+ '{} {}'.format(
+ self.discount_amount,
+ self.discount_name if self.discount_name else 'Discount'
+ )
+ ])
+ return display_str
+
+ @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
diff --git a/datacenterlight/static/datacenterlight/css/common.css b/datacenterlight/static/datacenterlight/css/common.css
index e24cf671..28674b30 100644
--- a/datacenterlight/static/datacenterlight/css/common.css
+++ b/datacenterlight/static/datacenterlight/css/common.css
@@ -1,7 +1,7 @@
body,
html {
width: 100%;
- min-height: 100%;
+ height: 100%;
}
body,
@@ -74,6 +74,20 @@ a.list-group-item-danger.active:focus {
padding: 10px;
}
+.navbar-brand > img {
+ height: 100%;
+}
+
+#logoWhite,
+.navbar-transparent #logoBlack {
+ display: none;
+}
+
+#logoBlack,
+.navbar-transparent #logoWhite {
+ display: block;
+}
+
@media (min-width: 768px) {
.navbar-right {
margin-right: 10px;
@@ -84,15 +98,85 @@ a.list-group-item-danger.active:focus {
}
}
+.navbar .dcl-link {
+ display: block;
+ padding: 15px;
+ color: #777;
+}
+
+.navbar .dcl-link:focus,
+.navbar .dcl-link:active,
+.navbar .dcl-link:hover {
+ text-decoration: none;
+}
+
+.navbar .dropdown-menu .dcl-link {
+ padding: 1px 10px;
+}
+
p.copyright {
- margin: 15px 0 0;
+ margin: 0;
}
footer {
- padding: 20px 0;
+ font-weight: 300;
+ padding: 25px 0;
background-color: #f8f8f8;
}
+footer .list-inline {
+ margin-bottom: 15px;
+}
+
footer a {
color: #777;
+}
+
+footer .dcl-link-separator {
+ position: relative;
+ padding-left: 10px;
+}
+
+footer .dcl-link-separator::before {
+ content: "";
+ position: absolute;
+ display: inline-block;
+ top: 9px;
+ bottom: 0;
+ left: -2px;
+ right: 0;
+ width: 2px;
+ height: 2px;
+ border-radius: 100%;
+ background: #777;
+}
+
+.mb-0 {
+ margin-bottom: 0;
+}
+
+.thin-hr {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+.payment-container .credit-card-info {
+ padding-bottom: 15px;
+ border-bottom: 1px solid #eee;
+}
+.credit-card-info {
+ display: flex;
+}
+
+.credit-card-info .align-bottom {
+ align-self: flex-end;
+ padding-right: 0 !important;
+}
+
+.new-card-head {
+ margin-top: 10px;
+}
+.new-card-button-margin button{
+ margin-top: 5px;
+ margin-bottom: 5px;
}
\ No newline at end of file
diff --git a/datacenterlight/static/datacenterlight/css/header-slider.css b/datacenterlight/static/datacenterlight/css/header-slider.css
index 9f5161d7..ea01edf7 100644
--- a/datacenterlight/static/datacenterlight/css/header-slider.css
+++ b/datacenterlight/static/datacenterlight/css/header-slider.css
@@ -1,3 +1,18 @@
+.btn-trans {
+ color: #fff;
+ border: 2px solid #fff;
+ padding: 4px 18px;
+ letter-spacing: 0.6px;
+ background: rgba(0,0,0,0.35);
+}
+
+.btn-trans:focus,
+.btn-trans:active,
+.btn-trans:hover {
+ background: #fff;
+ color: #333;
+}
+
.header_slider > .carousel .carousel-inner {
min-height: 95vh;
display: flex;
@@ -40,7 +55,7 @@
flex: 1;
}
-.header_slider > .carousel .item .container {
+.header_slider > .carousel .item .container-fluid {
overflow: auto;
padding: 50px 20px 60px;
height: 100%;
@@ -89,9 +104,9 @@
.header_slider .carousel-control .fa {
font-size: 4em;
}
- .header_slider > .carousel .item .container {
+ .header_slider > .carousel .item .container-fluid {
overflow: auto;
- padding: 75px 50px;
+ padding: 75px;
}
.header_slider .btn-trans {
padding: 8px 15px;
@@ -168,12 +183,4 @@
/* width: auto; */
height: 100%;
}
-}
-
-.btn-trans {
- color: #fff;
- border: 2px solid #fff;
- padding: 4px 18px;
- letter-spacing: 0.6px;
- background: rgba(0,0,0,0.35);
}
\ No newline at end of file
diff --git a/datacenterlight/static/datacenterlight/css/hosting.css b/datacenterlight/static/datacenterlight/css/hosting.css
index 800fb533..0f16ab77 100644
--- a/datacenterlight/static/datacenterlight/css/hosting.css
+++ b/datacenterlight/static/datacenterlight/css/hosting.css
@@ -1,3 +1,12 @@
+.navbar-transparent #logoWhite {
+ display: none;
+}
+
+.navbar-transparent #logoBlack {
+ display: block;
+ width: 220px;
+}
+
.topnav .navbar-fixed-top .navbar-collapse {
max-height: 740px;
}
@@ -15,8 +24,8 @@
}
@media(min-width: 768px) {
- .navbar-default .navbar-nav>li>a,
- .navbar-right .highlights-dropdown .dropdown-menu>li>a {
+ .navbar-default .navbar-nav>li a,
+ .navbar-right .highlights-dropdown .dropdown-menu>li a {
font-weight: 300;
}
.navbar-right .highlights-dropdown .dropdown-menu {
@@ -26,7 +35,7 @@
}
}
-.navbar-right .highlights-dropdown .dropdown-menu>li>a {
+.navbar-right .highlights-dropdown .dropdown-menu>li a {
font-size: 13px;
font-family: 'Lato', sans-serif;
padding: 1px 10px 1px 18px !important;
@@ -34,9 +43,9 @@
color: #333;
}
-.navbar-right .highlights-dropdown .dropdown-menu>li>a:hover,
-.navbar-right .highlights-dropdown .dropdown-menu>li>a:focus,
-.navbar-right .highlights-dropdown .dropdown-menu>li>a:active {
+.navbar-right .highlights-dropdown .dropdown-menu>li a:hover,
+.navbar-right .highlights-dropdown .dropdown-menu>li a:focus,
+.navbar-right .highlights-dropdown .dropdown-menu>li a:active {
background: transparent;
text-decoration: underline !important;
}
@@ -144,9 +153,9 @@
}
@media (max-width: 767px) {
- .navbar-default .navbar-nav .open .dropdown-menu>.active>a,
- .navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,
- .navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover {
+ .navbar-default .navbar-nav .open .dropdown-menu>.active a,
+ .navbar-default .navbar-nav .open .dropdown-menu>.active a:focus,
+ .navbar-default .navbar-nav .open .dropdown-menu>.active a:hover {
background-color: transparent;
}
}
@@ -163,7 +172,7 @@
}
.content-dashboard {
- min-height: calc(100vh - 60px);
+ min-height: calc(100vh - 96px);
width: 100%;
margin: 0 auto;
max-width: 1120px;
@@ -473,6 +482,7 @@
margin: 100px auto 40px;
border: 1px solid #ccc;
padding: 30px 30px 20px;
+ color: #595959;
}
.order-detail-container .dashboard-title-thin {
@@ -494,10 +504,6 @@
margin-bottom: 15px;
}
-.order-detail-container .order-details strong {
- color: #595959;
-}
-
.order-detail-container h4 {
font-size: 16px;
font-weight: bold;
@@ -506,13 +512,28 @@
.order-detail-container p {
margin-bottom: 5px;
- color: #595959;
}
.order-detail-container hr {
margin: 15px 0;
}
+.order-detail-container .thin-hr {
+ margin: 10px 0;
+}
+
+.order-detail-container .subtotal-price {
+ font-size: 16px;
+}
+
+.order-detail-container .subtotal-price .text-primary {
+ font-size: 17px;
+}
+
+.order-detail-container .total-price {
+ font-size: 18px;
+}
+
@media (max-width: 767px) {
.order-detail-container {
padding: 15px;
diff --git a/datacenterlight/static/datacenterlight/css/landing-page.css b/datacenterlight/static/datacenterlight/css/landing-page.css
index 61128e69..f241ed71 100755
--- a/datacenterlight/static/datacenterlight/css/landing-page.css
+++ b/datacenterlight/static/datacenterlight/css/landing-page.css
@@ -58,6 +58,16 @@ textarea {
min-width: 180px;
}
+.lead {
+ font-size: 18px;
+}
+
+@media (min-width: 768px) {
+ .lead {
+ font-size: 21px;
+ }
+}
+
/* Top navbar */
@@ -85,15 +95,13 @@ textarea {
}
}
+.navbar-transparent .navbar-nav>li a,
.navbar-transparent .navbar-nav>.open>a,
.navbar-transparent .navbar-nav>.open>a:focus,
.navbar-transparent .navbar-nav>.open>a:hover {
color: #fff;
}
-.navbar-transparent .navbar-nav>li a {
- color: #fff;
-}
.navbar-transparent .navbar-nav>li a:focus,
.navbar-transparent .navbar-nav>li a:active,
@@ -103,20 +111,10 @@ textarea {
text-decoration: none;
}
-.navbar .dcl-link {
- display: block;
- padding: 15px;
- color: #777;
-}
-
-.navbar .dcl-link:focus,
-.navbar .dcl-link:active,
-.navbar .dcl-link:hover {
- text-decoration: none;
-}
-
-.navbar .dropdown-menu .dcl-link {
- padding: 1px 10px;
+.topnav .nav .open>a,
+.topnav .nav .open>a:focus,
+.topnav .nav .open>a:hover {
+ background: transparent;
}
.navbar-transparent .navbar-nav>li>.on-hover-border {
@@ -139,17 +137,6 @@ textarea {
color: #fff;
}
-#logoWhite,
-.navbar-transparent #logoBlack {
- display: none;
-}
-
-#logoBlack,
-.navbar-transparent #logoWhite {
- display: block;
- width: 220px;
-}
-
.nav-language {
position: relative;
}
@@ -394,25 +381,26 @@ textarea {
color: #5A74AF;
}
-.split-section .split-text .lead {
- font-size: 21px;
- color: #3a3a3a;
- font-weight: 300 !important;
+.split-section h2 {
+ font-size: 36px;
+ font-weight: 400;
}
-.split-section .split-text h2 {
+.split-section .split-title-plain h2 {
font-size: 40px;
+ font-weight: 300;
line-height: 50px;
color: #3a3a3a;
}
-.split-section .split-text .split-title {
+.split-section .split-title {
position: relative;
margin-bottom: 25px;
}
-.split-section .split-text .split-title h2 {
+.split-section .split-title h2 {
font-size: 50px;
+ font-weight: 300;
padding-bottom: 25px;
letter-spacing: 2px;
}
@@ -424,10 +412,22 @@ textarea {
}
.split-section.left .split-description {
-/* width: 90%; */
margin-right: auto;
}
+.split-section .split-description .lead {
+ color: #3a3a3a;
+}
+
+@media (min-width: 768px) {
+ .split-section .split-description .lead {
+ font-size: 21px;
+ }
+ .split-section .space .split-description .lead {
+ font-size: 20px;
+ }
+}
+
.split-section.right .split-description {
width: 90%;
margin-left: auto;
@@ -440,15 +440,17 @@ textarea {
}
.split-section.right .split-text ul,
-.split-section.left .split-text {
+.split-section.left .split-text,
+.split-section.left .space {
text-align: left;
}
-.split-section.right .split-text {
+.split-section.right .split-text,
+.split-section.right .space {
text-align: right;
}
-.split-section .split-text .split-title::before {
+.split-section .split-title::before {
content: "";
position: absolute;
bottom: 0;
@@ -458,11 +460,11 @@ textarea {
left: auto;
}
-.split-section.right .split-text .split-title::before {
+.split-section.right .split-title::before {
right: 0;
}
-.split-section.left .split-text .split-title::before {
+.split-section.left .split-title::before {
left: 0;
}
@@ -501,10 +503,15 @@ textarea {
.split-section-plain .split-figure {
width: 41.66666667%;
}
-
+ .split-section-plain .split-figure.col-sm-pull-6 {
+ right: 58.33333333%;
+ }
.split-section-plain .split-text {
width: 58.33333333%;
}
+ .split-section-plain .split-text.col-sm-push-6 {
+ left: 41.66666667%;
+ }
}
.section-image img {
@@ -515,6 +522,7 @@ textarea {
padding-top: 20px;
display: inline-block;
color: #999 !important;
+ word-break: break-all;
}
.price-calc-section .card {
@@ -726,34 +734,9 @@ textarea {
width: 70%;
}
-hr.thick-divider {
- border-top: 3px solid #eee !important;
-}
-
.space {
- padding: 50px 0;
-}
-
-tech-sub-sec h2 {
- font-size: 45px;
- line-height: 60px;
- padding-bottom: 25px;
- color: #3a3a3a;
- letter-spacing: 1px;
-}
-
-.logo-wrap {
- text-align: center;
- min-height: 140px;
- padding: 20px 40px 30px 40px;
-}
-
-.btm-space {
- padding-bottom: 8px;
-}
-
-.btm-space-tayga {
- padding-bottom: 12px;
+ max-width: 660px;
+ margin: auto;
}
.percent-text {
@@ -761,11 +744,6 @@ tech-sub-sec h2 {
color: #999;
}
-.tech-sub-sec h2 {
- font-size: 40px;
- line-height: 55px;
-}
-
.space-middle {
/* padding: 45px 0; */
display: inline-block;
@@ -773,17 +751,11 @@ tech-sub-sec h2 {
.ssdimg {
margin: 0 15px;
- /* vertical-align: middle; */
- /* display: inline-block; */
-}
-
-.ssdimg img {
- max-width: 125px;
}
@media (max-width: 767px) {
.ssdimg img {
- width: 100px;
+ max-height: 120px;
}
}
@@ -791,16 +763,6 @@ tech-sub-sec h2 {
padding: 30px 2px 20px;
}
-.logo-wrap .logo-caption {
- padding-top: 20px;
- display: inline-block;
- color: #999 !important;
-}
-
-.logo-wrap-1 {
- padding-top: 50px;
-}
-
/*Pricing page*/
@@ -814,7 +776,7 @@ tech-sub-sec h2 {
width: 100%;
margin: 0 auto;
background: #fff;
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1), 0 0 6px rgba(0, 0, 0, 0.15);
padding-bottom: 40px;
border-radius: 7px;
text-align: center;
@@ -966,20 +928,14 @@ tech-sub-sec h2 {
}
}
-@media (min-width: 576px) and (max-width: 767px) {
- .logo-wrap, .logo-wrap-1 {
- width: 50%;
- padding: 15px 30px !important;
- min-height: 179px;
- }
-}
-@media(max-width:991px) {
- .section-sm-center .split-text {
+@media(max-width:767px) {
+ .section-sm-center .split-text,
+ .section-sm-center .space {
text-align: center !important;
margin-bottom: 40px;
}
- .section-sm-center .split-text .split-title::before {
+ .section-sm-center .split-title::before {
left: 50% !important;
transform: translate(-50%, 0);
}
@@ -989,9 +945,6 @@ tech-sub-sec h2 {
}
@media(max-width:767px) {
- .logo-wrap {
- padding: 10px;
- }
.navbar-transparent li a {
color: #777 !important;
}
@@ -1076,13 +1029,16 @@ tech-sub-sec h2 {
.split-section .icon-section i {
font-size: 120px;
}
- .split-section .split-text h2 {
+ .split-section h2 {
+ font-size: 28px;
+ }
+ .split-section .split-title-plain h2 {
font-size: 30px;
line-height: 35px;
}
- .split-section .split-text .split-title h2 {
- font-size: 35px;
- line-height: 35px;
+ .split-section .split-title h2 {
+ font-size: 32px;
+ line-height: 34px;
}
.contact-section .title {
margin: 0 auto;
@@ -1138,9 +1094,6 @@ tech-sub-sec h2 {
}
@media(max-width:575px) {
- .logo-wrap {
- padding: 30px;
- }
.percent-text {
font-weight: normal;
font-size: 37px;
@@ -1210,12 +1163,16 @@ footer {
flex-shrink: 0;
padding: 0 15px;
}
+ .flex-row .desc-text {
+ text-align: right;
+ }
.flex-row .desc-text,
.flex-row .percent-text {
- max-width: 380px;
+ max-width: 430px;
}
.flex-row-rev .desc-text {
- max-width: 710px;
+ max-width: 600px;
+ text-align: left;
}
.flex-row-rev .percent-text {
order: 2;
@@ -1225,10 +1182,6 @@ footer {
}
}
-.w380 {
- max-width: 380px !important;
-}
-
.checkmark {
display: inline-block;
}
@@ -1247,24 +1200,6 @@ footer {
transform: rotate(45deg);
}
-footer .dcl-link-separator {
- position: relative;
- padding-left: 10px;
-}
-
-footer .dcl-link-separator::before {
- content: "";
- position: absolute;
- display: inline-block;
- top: 9px;
- bottom: 0;
- left: -2px;
- right: 0;
- width: 2px;
- height: 2px;
- border-radius: 100%;
- background: #777;
-}
/* new styles for whydcl section cms plugin (to replace older style) */
@@ -1282,3 +1217,88 @@ footer .dcl-link-separator::before {
font-size: 30px;
}
}
+
+
+/* cms section promo */
+
+.promo-section {
+ padding: 75px 15px;
+}
+
+.promo-section.promo-with-bg {
+ color: #fff;
+ background-size: cover;
+ background-position: center;
+}
+
+.promo-section.promo-with-bg a {
+ color: #87B6EA;
+}
+
+.promo-section.promo-with-bg a:hover,
+.promo-section.promo-with-bg a:focus {
+ color: #77a6da;
+}
+
+.promo-section h3 {
+ font-weight: 700;
+ font-size: 36px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-top: 10px;
+ margin-bottom: 25px;
+}
+
+.promo-section h4 {
+ font-size: 24px;
+ margin-bottom: 20px;
+}
+
+.promo-section p {
+ font-size: 18px;
+ line-height: 1.5;
+}
+
+.promo-section.text-center p {
+ max-width: 720px;
+ margin: auto;
+}
+
+.promo-section.text-center h3,
+.promo-section.text-center h4 {
+ margin-bottom: 35px;
+}
+
+.split-text .split-subsection {
+ margin-top: 25px;
+ margin-bottom: 25px;
+}
+
+.split-text .promo-section {
+ padding: 20px 15px;
+ margin-top: 30px;
+ margin-bottom: 30px;
+}
+
+.split-text .promo-section .container {
+ width: auto;
+}
+
+.split-text .promo-section h3,
+.split-text .promo-section h4 {
+ margin-bottom: 15px;
+}
+
+@media (max-width: 767px) {
+ .split-text .split-subsection {
+ margin-left: -15px;
+ margin-right: -15px;
+ }
+ .promo-section h3 {
+ font-size: 29px;
+ }
+ .split-text .promo-section {
+ padding-left: 0;
+ padding-right: 0;
+ }
+}
\ No newline at end of file
diff --git a/datacenterlight/static/datacenterlight/img/datacenterlight.png b/datacenterlight/static/datacenterlight/img/datacenterlight.png
index 1ae6ff53..9097af9b 100644
Binary files a/datacenterlight/static/datacenterlight/img/datacenterlight.png and b/datacenterlight/static/datacenterlight/img/datacenterlight.png differ
diff --git a/datacenterlight/static/datacenterlight/img/logo_black.svg b/datacenterlight/static/datacenterlight/img/logo_black.svg
index 8c245f9c..7845f7b5 100644
--- a/datacenterlight/static/datacenterlight/img/logo_black.svg
+++ b/datacenterlight/static/datacenterlight/img/logo_black.svg
@@ -2,32 +2,32 @@
diff --git a/datacenterlight/static/datacenterlight/js/main.js b/datacenterlight/static/datacenterlight/js/main.js
index 10412824..292e8c16 100644
--- a/datacenterlight/static/datacenterlight/js/main.js
+++ b/datacenterlight/static/datacenterlight/js/main.js
@@ -104,17 +104,33 @@
});
$('.url').click(function(event) {
event.preventDefault();
- var href = $(this).attr('href');
+ var $this = $(this);
+ var href = $this.attr('href');
$('.navbar-collapse').removeClass('in');
$('.navbar-collapse').addClass('collapsing');
- if ($(href).length) {
- $('html, body').animate({
- scrollTop: $(href).offset().top - 50
- }, 1000);
+ if (href[0] === "#") {
+ scrollToElement(href);
+ } else if (href) {
+ var path = $(this).prop('href').split('#');
+ var currentPath = window.location.origin + window.location.pathname;
+ if (currentPath == path[0] && path[1]) {
+ scrollToElement('#' + path[1]);
+ } else {
+ window.location = href;
+ }
}
});
}
+ function scrollToElement(el) {
+ var $el = $(el);
+ if ($el.length) {
+ $('html, body').animate({
+ scrollTop: $el.offset().top - 50
+ }, 1000);
+ }
+ }
+
function verifiedUrl() {
if (window.location.href.indexOf('#success') > -1) {
form_success();
@@ -155,7 +171,22 @@
}
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.ramUnitPrice = 2;
+ }
+ if(typeof window.ssdUnitPrice === 'undefined'){
+ window.ssdUnitPrice = 0.6;
+ }
+ if(typeof window.discountAmount === 'undefined'){
+ window.discountAmount = 0;
+ }
+ var total = (cardPricing['cpu'].value * window.coresUnitPrice) +
+ (cardPricing['ram'].value * window.ramUnitPrice) +
+ (cardPricing['storage'].value * window.ssdUnitPrice) -
+ window.discountAmount;
total = parseFloat(total.toFixed(2));
$("#total").text(total);
}
diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py
index 3db6eb54..2779f79b 100644
--- a/datacenterlight/tasks.py
+++ b/datacenterlight/tasks.py
@@ -1,23 +1,26 @@
from datetime import datetime
+from celery import current_task
from celery.exceptions import MaxRetriesExceededError
from celery.utils.log import get_task_logger
-from celery import current_task
from django.conf import settings
from django.core.mail import EmailMessage
from django.core.urlresolvers import reverse
from django.utils import translation
from django.utils.translation import ugettext_lazy as _
+from time import sleep
from dynamicweb.celery import app
-from hosting.models import HostingOrder, HostingBill
-from membership.models import StripeCustomer, CustomUser
+from hosting.models import HostingOrder
+from membership.models import CustomUser
from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import VirtualMachineSerializer
-from utils.hosting_utils import get_all_public_keys, get_or_create_vm_detail
-from utils.forms import UserBillingAddressForm
+from utils.hosting_utils import (
+ get_all_public_keys, get_or_create_vm_detail, ping_ok
+)
from utils.mailer import BaseEmail
-from utils.models import BillingAddress
+from utils.stripe_utils import StripeUtils
+from .models import VMPricing
logger = get_task_logger(__name__)
@@ -49,23 +52,15 @@ def retry_task(task, exception=None):
@app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES)
-def create_vm_task(self, vm_template_id, user, specs, template,
- stripe_customer_id, billing_address_data,
- stripe_subscription_id, cc_details):
+def create_vm_task(self, vm_template_id, user, specs, template, order_id):
logger.debug(
"Running create_vm_task on {}".format(current_task.request.hostname))
vm_id = None
try:
- final_price = specs.get('price')
- 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']
+ final_price = (
+ specs.get('total_price') if 'total_price' in specs
+ else specs.get('price')
)
- billing_address.save()
- customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
if 'pass' in user:
on_user = user.get('email')
@@ -94,33 +89,43 @@ def create_vm_task(self, vm_template_id, user, specs, template,
if vm_id is None:
raise Exception("Could not create VM")
- # Create a Hosting Order
- order = HostingOrder.create(
- price=final_price,
- vm_id=vm_id,
- customer=customer,
- billing_address=billing_address
+ # Update HostingOrder with the created vm_id
+ hosting_order = HostingOrder.objects.filter(id=order_id).first()
+ error_msg = None
+
+ try:
+ hosting_order.vm_id = vm_id
+ hosting_order.save()
+ logger.debug(
+ "Updated hosting_order {} with vm_id={}".format(
+ hosting_order.id, vm_id
+ )
+ )
+ except Exception as ex:
+ error_msg = (
+ "HostingOrder with id {order_id} not found. This means that "
+ "the hosting order was not created and/or it is/was not "
+ "associated with VM with id {vm_id}. Details {details}".format(
+ order_id=order_id, vm_id=vm_id, details=str(ex)
+ )
+ )
+ logger.error(error_msg)
+
+ stripe_utils = StripeUtils()
+ result = stripe_utils.set_subscription_metadata(
+ subscription_id=hosting_order.subscription_id,
+ metadata={"VM_ID": str(vm_id)}
)
- # Create a Hosting Bill
- HostingBill.create(
- customer=customer, billing_address=billing_address)
-
- # Create Billing Address for User if he does not have one
- if not customer.user.billing_addresses.count():
- billing_address_data.update({
- 'user': customer.user.id
- })
- billing_address_user_form = UserBillingAddressForm(
- billing_address_data)
- billing_address_user_form.is_valid()
- billing_address_user_form.save()
-
- # Associate an order with a stripe subscription
- order.set_subscription_id(stripe_subscription_id, cc_details)
-
- # If the Stripe payment succeeds, set order status approved
- order.set_approved()
+ if result.get('error') is not None:
+ emsg = "Could not update subscription metadata for {sub}".format(
+ sub=hosting_order.subscription_id
+ )
+ logger.error(emsg)
+ if error_msg:
+ error_msg += ". " + emsg
+ else:
+ error_msg = emsg
vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
@@ -130,12 +135,19 @@ def create_vm_task(self, vm_template_id, user, specs, template,
'cores': specs.get('cpu'),
'memory': specs.get('memory'),
'storage': specs.get('disk_size'),
- 'price': specs.get('price'),
+ 'price': final_price,
'template': template.get('name'),
'vm_name': vm.get('name'),
'vm_id': vm['vm_id'],
- 'order_id': order.id
+ 'order_id': 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,
@@ -159,7 +171,7 @@ def create_vm_task(self, vm_template_id, user, specs, template,
'base_url': "{0}://{1}".format(user.get('request_scheme'),
user.get('request_host')),
'order_url': reverse('hosting:orders',
- kwargs={'pk': order.id}),
+ kwargs={'pk': order_id}),
'page_header': _(
'Your New VM %(vm_name)s at Data Center Light') % {
'vm_name': vm.get('name')},
@@ -176,11 +188,11 @@ def create_vm_task(self, vm_template_id, user, specs, template,
email = BaseEmail(**email_data)
email.send()
- # try to see if we have the IP and that if the ssh keys can
- # be configured
- new_host = manager.get_primary_ipv4(vm_id)
+ # try to see if we have the IPv6 of the new vm and that if the ssh
+ # keys can be configured
+ vm_ipv6 = manager.get_ipv6(vm_id)
logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
- if new_host is not None:
+ if vm_ipv6 is not None:
custom_user = CustomUser.objects.get(email=user.get('email'))
get_or_create_vm_detail(custom_user, manager, vm_id)
if custom_user is not None:
@@ -191,13 +203,48 @@ def create_vm_task(self, vm_template_id, user, specs, template,
logger.debug(
"Calling configure on {host} for "
"{num_keys} keys".format(
- host=new_host, num_keys=len(keys)))
- # Let's delay the task by 75 seconds to be sure
- # that we run the cdist configure after the host
- # is up
- manager.manage_public_key(keys,
- hosts=[new_host],
- countdown=75)
+ host=vm_ipv6, num_keys=len(keys)
+ )
+ )
+ # Let's wait until the IP responds to ping before we
+ # run the cdist configure on the host
+ did_manage_public_key = False
+ for i in range(0, 15):
+ if ping_ok(vm_ipv6):
+ logger.debug(
+ "{} is pingable. Doing a "
+ "manage_public_key".format(vm_ipv6)
+ )
+ sleep(10)
+ manager.manage_public_key(
+ keys, hosts=[vm_ipv6]
+ )
+ did_manage_public_key = True
+ break
+ else:
+ logger.debug(
+ "Can't ping {}. Wait 5 secs".format(
+ vm_ipv6
+ )
+ )
+ sleep(5)
+ if not did_manage_public_key:
+ emsg = ("Waited for over 75 seconds for {} to be "
+ "pingable. But the VM was not reachable. "
+ "So, gave up manage_public_key. Please do "
+ "this manually".format(vm_ipv6))
+ logger.error(emsg)
+ email_data = {
+ 'subject': '{} CELERY TASK INCOMPLETE: {} not '
+ 'pingable for 75 seconds'.format(
+ settings.DCL_TEXT, vm_ipv6
+ ),
+ 'from_email': current_task.request.hostname,
+ 'to': settings.DCL_ERROR_EMAILS_TO_LIST,
+ 'body': emsg
+ }
+ email = EmailMessage(**email_data)
+ email.send()
except Exception as e:
logger.error(str(e))
try:
diff --git a/datacenterlight/templates/datacenterlight/base.html b/datacenterlight/templates/datacenterlight/base.html
index 8bdfb65f..75cb8de2 100644
--- a/datacenterlight/templates/datacenterlight/base.html
+++ b/datacenterlight/templates/datacenterlight/base.html
@@ -9,7 +9,7 @@
-
+