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 aa0d73bf..b85dd4ff 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,90 @@ +Next: + * bugfix: Use correct version of django-multisite (MR #676) +2.4.1: 2018-10-18 + * bugfix: Update pycryptodome module from 3.4 to 3.6.6 (PR #674) +2.4: 2018-10-18 + * #5681: [hosting,dcl] Allow admin to lower minimum RAM to 512 MB (PR #672) +2.3.1: 2018-10-17 + * bugfix: [hosting, dcl] Show VAT percent rounded to 2 decimal places in the order confirmation page (PR #673) +2.3: 2018-10-08 + * #5690: Generic payment page - allow admin to add a onetime/monthly product and the frontend for user to pay for this product (PR #666) +2.2.2: 2018-09-28 + * #5721: Set calculator OS list in alphabetical order and set `Devuan Ascii` as the default (PR #668) + * bugfix: Fix some typos and correct DE translations (PR #667) +2.2.1: 2018-09-25 + * feature: Change DCLNavbarPlugin to show login option only if set (PR #665) + * bugfix: Log opennebula errors and send proper message when vm terminate is not completed in the stipulated time (PR #648) +2.2: 2018-09-06 + * bugfix: Include price in the Stripe plan name to make it distinct and to correct pricing since version 1.9 +2.1.2: 2018-08-30 + * bugfix: [blog, comic] Set blog rss feed for all blog templates +2.1.1: 2018-08-24 + * #5487: [hosting] Add explicit warning message for teminating VM (PR #656) + * bugfix: [dg] Send email to admin on dg subscription and increase cc_brand field to 128 characters (PR #652) + * #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 @@ -63,7 +150,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 8c9ae740..2d1a98b5 100644
--- a/datacenterlight/cms_models.py
+++ b/datacenterlight/cms_models.py
@@ -1,12 +1,56 @@
+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,
@@ -136,6 +180,10 @@ class DCLNavbarPluginModel(CMSPlugin):
default=True,
help_text='Select to include the language selection dropdown.'
)
+ show_login_option = models.BooleanField(
+ default=True,
+ help_text='Uncheck this if you do not want to show login/dashboard.'
+ )
def get_logo_dark(self):
# used only if atleast one logo exists
@@ -160,7 +208,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(
@@ -176,7 +224,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'
)
@@ -249,3 +297,68 @@ class DCLSectionPromoPluginModel(CMSPlugin):
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."
+ )
+ default_selected_template = models.CharField(
+ default="Devuan Ascii",
+ null=True,
+ max_length=128,
+ help_text="Write the name of the template that you need selected as"
+ " default when the calculator loads"
+ )
+ enable_512mb_ram = models.BooleanField(default=False)
diff --git a/datacenterlight/cms_plugins.py b/datacenterlight/cms_plugins.py
index a1a3833d..c3ec974f 100644
--- a/datacenterlight/cms_plugins.py
+++ b/datacenterlight/cms_plugins.py
@@ -6,9 +6,10 @@ from .cms_models import (
DCLFooterPluginModel, DCLLinkPluginModel, DCLNavbarDropdownPluginModel,
DCLSectionIconPluginModel, DCLSectionImagePluginModel,
DCLSectionPluginModel, DCLNavbarPluginModel,
- DCLSectionPromoPluginModel
+ DCLSectionPromoPluginModel, DCLCalculatorPluginModel
)
from .models import VMTemplate
+from datacenterlight.utils import clear_all_session_vars
@plugin_pool.register_plugin
@@ -21,7 +22,7 @@ class DCLSectionPlugin(CMSPluginBase):
allow_children = True
child_classes = [
'DCLSectionIconPlugin', 'DCLSectionImagePlugin',
- 'DCLSectionPromoPlugin', 'UngleichHTMLPlugin'
+ 'DCLSectionPromoPlugin', 'UngleichHTMLPlugin', 'DCLCalculatorPlugin'
]
def render(self, context, instance, placeholder):
@@ -30,14 +31,17 @@ class DCLSectionPlugin(CMSPluginBase):
)
context['children_to_side'] = []
context['children_to_content'] = []
+ context['children_calculator'] = []
if instance.child_plugin_instances is not None:
right_children = [
'DCLSectionImagePluginModel',
- 'DCLSectionIconPluginModel'
+ '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
@@ -76,25 +80,27 @@ class DCLSectionPromoPlugin(CMSPluginBase):
class DCLCalculatorPlugin(CMSPluginBase):
module = "Datacenterlight"
name = "DCL Calculator Plugin"
- model = DCLSectionPluginModel
+ model = DCLCalculatorPluginModel
render_template = "datacenterlight/cms/calculator.html"
cache = False
- allow_children = True
- child_classes = [
- 'DCLSectionPromoPlugin', 'UngleichHTMLPlugin'
- ]
+ require_parent = True
def render(self, context, instance, placeholder):
+ clear_all_session_vars(context['request'])
context = super(DCLCalculatorPlugin, self).render(
context, instance, placeholder
)
- context['templates'] = VMTemplate.objects.all()
- context['children_to_side'] = []
- context['children_to_content'] = []
- if instance.child_plugin_instances is not None:
- context['children_to_content'].extend(
- instance.child_plugin_instances
- )
+ 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).order_by('name')
+ else:
+ context['templates'] = VMTemplate.objects.filter(
+ vm_type=instance.vm_type
+ ).order_by('name')
+ context['instance'] = instance
+ context['min_ram'] = 0.5 if instance.enable_512mb_ram else 1
return context
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..d43e91ea 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
{{base_url}}{{activation_link}}
{% if account_details %}
{% url 'hosting:reset_password' as reset_password_url %}
+
{% trans "Your account details are as follows" %}:
-
- {% trans "Username" %} : {% trans "Your email address" %}
+
+ {% trans "Username" %} : {% trans "Your email address" %}
+ {% blocktrans %}You can reset your password here.{% endblocktrans %}
+ {% trans "Cores"%} {{request.session.specs.cpu|floatformat}} {% trans "Memory"%} {{request.session.specs.memory|floatformat}} GB {% trans "Disk space"%} {{request.session.specs.disk_size|floatformat}} GB {% trans "Configuration"%} {{request.session.template.name}} {%trans "Total" %} ({%trans "including VAT" %}) {{request.session.specs.price}} CHF/{% trans "Month" %} {% trans "Cores"%} {{request.session.specs.cpu|floatformat}} {% trans "Memory"%} {{request.session.specs.memory|floatformat}} GB {% trans "Disk space"%} {{request.session.specs.disk_size|floatformat}} GB {% trans "Configuration"%} {{request.session.template.name}}
+ {%trans "Total" %}
+
+ ({% if vm_pricing.vat_inclusive %}{%trans "including VAT" %}{% else %}{%trans "excluding VAT" %}{% endif %})
+
+ {{request.session.specs.price|intcomma}} CHF/{% trans "Month" %}
+
+ {%trans "Discount" as discount_name %}
+ {{ vm_pricing.discount_name|default:discount_name }}
+ - {{ vm_pricing.discount_amount }} CHF/{% trans "Month" %}
+
+ ({% trans "Will be applied at checkout" %})
+
- {% blocktrans %}Please fill in your credit card information below. We are using Stripe for payment and do not store your information in our database.{% endblocktrans %}
-
- {% trans "You are not making any payment yet. After submitting your card information, you will be taken to the Confirm Order Page." %}
- {{ message|safe }}
- {{ error|escape }}
-
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 "
@@ -328,6 +293,9 @@ msgstr "Registrieren"
msgid "Billing Address"
msgstr "Rechnungsadresse"
+msgid "Make a payment"
+msgstr ""
+
msgid "Your Order"
msgstr "Deine Bestellung"
@@ -349,12 +317,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 +352,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"
@@ -397,24 +377,85 @@ 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 "Amount"
+msgstr ""
+
+msgid "Description"
+msgstr ""
+
+msgid "Recurring"
+msgstr ""
+
+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 %(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 ""
+"By clicking \"Place order\" this payment will charge your credit card "
+"account with a one time amount of %(total_price)s CHF"
+msgstr ""
+"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
+"%(vm_total_price)s CHF pro Monat belastet"
+
+#, python-format
+msgid ""
+"By clicking \"Place order\" this plan will charge your credit card account "
+"with %(vm_total_price)s CHF/month"
+msgstr ""
+"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 +541,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 +565,36 @@ 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 "Confirmation of your payment"
+msgstr ""
+
+msgid " This is a monthly recurring plan."
+msgstr ""
+
+#, python-brace-format
+msgid ""
+"Hi {name},\n"
+"\n"
+"thank you for your order!\n"
+"We have just received a payment of CHF {amount:.2f} from you.{recurring}\n"
+"\n"
+"Cheers,\n"
+"Your Data Center Light team"
+msgstr ""
+
+msgid "Thank you for the payment."
+msgstr "Danke für Deine Bestellung."
+
+msgid ""
+"You will soon receive a confirmation email of the payment. You can always "
+"contact us at info@ungleich.ch for any question that you may have."
+msgstr ""
+
msgid "Thank you for the order."
msgstr "Danke für Deine Bestellung."
@@ -523,36 +605,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 6a45ebad..89271dc4 100644
--- a/datacenterlight/management/commands/fetchvmtemplates.py
+++ b/datacenterlight/management/commands/fetchvmtemplates.py
@@ -10,16 +10,28 @@ class Command(BaseCommand):
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.lstrip('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()
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/migrations/0025_dclnavbarpluginmodel_show_login_option.py b/datacenterlight/migrations/0025_dclnavbarpluginmodel_show_login_option.py
new file mode 100644
index 00000000..e9ec57ba
--- /dev/null
+++ b/datacenterlight/migrations/0025_dclnavbarpluginmodel_show_login_option.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-09-25 20:27
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0024_dclcalculatorpluginmodel_vm_templates_to_show'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='dclnavbarpluginmodel',
+ name='show_login_option',
+ field=models.BooleanField(default=True, help_text='Uncheck this if you do not want to show login/dashboard.'),
+ ),
+ ]
diff --git a/datacenterlight/migrations/0026_dclcalculatorpluginmodel_default_selected_template.py b/datacenterlight/migrations/0026_dclcalculatorpluginmodel_default_selected_template.py
new file mode 100644
index 00000000..047d4096
--- /dev/null
+++ b/datacenterlight/migrations/0026_dclcalculatorpluginmodel_default_selected_template.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-09-27 20:32
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0025_dclnavbarpluginmodel_show_login_option'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='dclcalculatorpluginmodel',
+ name='default_selected_template',
+ field=models.CharField(default='Devuan Ascii', help_text='Write the name of the template that you need selected as default when the calculator loads', max_length=128, null=True),
+ ),
+ ]
diff --git a/datacenterlight/migrations/0027_dclcalculatorpluginmodel_enable_512mb_ram.py b/datacenterlight/migrations/0027_dclcalculatorpluginmodel_enable_512mb_ram.py
new file mode 100644
index 00000000..bd639c9d
--- /dev/null
+++ b/datacenterlight/migrations/0027_dclcalculatorpluginmodel_enable_512mb_ram.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2018-09-29 05:36
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0026_dclcalculatorpluginmodel_default_selected_template'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='dclcalculatorpluginmodel',
+ name='enable_512mb_ram',
+ field=models.BooleanField(default=False),
+ ),
+ ]
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..00ee52cc 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,91 @@ 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;
-}
\ No newline at end of file
+}
+
+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;
+}
+
+.input-no-border {
+ border: none !important;
+ background: transparent !important;
+ resize: none;
+}
diff --git a/datacenterlight/static/datacenterlight/css/header-slider.css b/datacenterlight/static/datacenterlight/css/header-slider.css
index e21e2b49..ea01edf7 100644
--- a/datacenterlight/static/datacenterlight/css/header-slider.css
+++ b/datacenterlight/static/datacenterlight/css/header-slider.css
@@ -55,7 +55,7 @@
flex: 1;
}
-.header_slider > .carousel .item .container {
+.header_slider > .carousel .item .container-fluid {
overflow: auto;
padding: 50px 20px 60px;
height: 100%;
@@ -104,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;
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 3ac46295..f241ed71 100755
--- a/datacenterlight/static/datacenterlight/css/landing-page.css
+++ b/datacenterlight/static/datacenterlight/css/landing-page.css
@@ -89,25 +89,19 @@ textarea {
border-radius: 6px;
}
-.navbar-brand > img {
- max-height: 30px;
-}
-
@media (max-width: 767px) {
.navbar-default .navbar-nav>li>a{
font-weight: 400;
}
}
+.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,
@@ -123,22 +117,6 @@ textarea {
background: transparent;
}
-.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;
-}
-
.navbar-transparent .navbar-nav>li>.on-hover-border {
transition: all 0.3s linear;
box-shadow: none;
@@ -159,17 +137,6 @@ textarea {
color: #fff;
}
-#logoWhite,
-.navbar-transparent #logoBlack {
- display: none;
-}
-
-#logoBlack,
-.navbar-transparent #logoWhite {
- display: block;
- width: 220px;
-}
-
.nav-language {
position: relative;
}
@@ -809,7 +776,7 @@ textarea {
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;
@@ -962,7 +929,7 @@ textarea {
}
-@media(max-width:991px) {
+@media(max-width:767px) {
.section-sm-center .split-text,
.section-sm-center .space {
text-align: center !important;
@@ -1233,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,6 +1231,15 @@ footer .dcl-link-separator::before {
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;
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/js/main.js b/datacenterlight/static/datacenterlight/js/main.js
index dc5a9108..65db1d6b 100644
--- a/datacenterlight/static/datacenterlight/js/main.js
+++ b/datacenterlight/static/datacenterlight/js/main.js
@@ -5,6 +5,10 @@
/* ---------------------------------------------
Scripts initialization
--------------------------------------------- */
+ var minRam = 1;
+ if(window.minRam){
+ minRam = window.minRam;
+ }
var cardPricing = {
'cpu': {
'id': 'coreValue',
@@ -16,7 +20,7 @@
'ram': {
'id': 'ramValue',
'value': 2,
- 'min': 1,
+ 'min': minRam,
'max': 200,
'interval': 1
},
@@ -40,6 +44,7 @@
_initNavUrl();
_initPricing();
ajaxForms();
+ $('#ramValue').data('old-value', $('#ramValue').val());
});
$(window).resize(function() {
@@ -113,10 +118,10 @@
} else if (href) {
var path = $(this).prop('href').split('#');
var currentPath = window.location.origin + window.location.pathname;
- if (!path[1]) {
- window.location = href;
- } else if (currentPath == path[0]) {
+ if (currentPath == path[0] && path[1]) {
scrollToElement('#' + path[1]);
+ } else {
+ window.location = href;
}
}
});
@@ -144,21 +149,54 @@
var data = $(this).data('minus');
if (cardPricing[data].value > cardPricing[data].min) {
- cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
+ if(data === 'ram' && String(cardPricing[data].value) === "1" && minRam === 0.5){
+ cardPricing[data].value = 0.5;
+ $('#ramValue').val('0.5');
+ $("#ramValue").attr('step', 0.5);
+ } else {
+ cardPricing[data].value = Number(cardPricing[data].value) - cardPricing[data].interval;
+ }
}
_fetchPricing();
+ $('#ramValue').data('old-value', $('#ramValue').val());
});
$('.fa-plus-circle.right').click(function(event) {
var data = $(this).data('plus');
if (cardPricing[data].value < cardPricing[data].max) {
- cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
+ if(data === 'ram' && String(cardPricing[data].value) === "0.5" && minRam === 0.5){
+ cardPricing[data].value = 1;
+ $('#ramValue').val('1');
+ $("#ramValue").attr('step', 1);
+ } else {
+ cardPricing[data].value = Number(cardPricing[data].value) + cardPricing[data].interval;
+ }
}
_fetchPricing();
+ $('#ramValue').data('old-value', $('#ramValue').val());
});
$('.input-price').change(function() {
var data = $(this).attr("name");
- cardPricing[data].value = $('input[name=' + data + ']').val();
+ var input = $('input[name=' + data + ']');
+ var inputValue = input.val();
+
+ if(data === 'ram') {
+ var ramInput = $('#ramValue');
+ if ($('#ramValue').data('old-value') < $('#ramValue').val()) {
+ if($('#ramValue').val() === '1' && minRam === 0.5) {
+ $("#ramValue").attr('step', 1);
+ $('#ramValue').val('1');
+ }
+ } else {
+ if($('#ramValue').val() === '0' && minRam === 0.5) {
+ $("#ramValue").attr('step', 0.5);
+ $('#ramValue').val('0.5');
+ }
+ }
+ inputValue = $('#ramValue').val();
+ $('#ramValue').data('old-value', $('#ramValue').val());
+ }
+ cardPricing[data].value = inputValue;
_fetchPricing();
});
}
@@ -171,7 +209,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 @@
-
+
{% page_attribute page_title %}
+ {% page_attribute "page_title" %}
-
+
+
{% else %}
-
-
+
+
{% endif %}
-
+
@@ -33,18 +33,19 @@
{% trans "Password" %} : {{account_details}}
-
- {% trans "You can reset your password here" %}:
- {{base_url}}{{reset_password_url}}
+
diff --git a/datacenterlight/templates/datacenterlight/emails/welcome_user.html b/datacenterlight/templates/datacenterlight/emails/welcome_user.html
index e947ac97..f18f9750 100644
--- a/datacenterlight/templates/datacenterlight/emails/welcome_user.html
+++ b/datacenterlight/templates/datacenterlight/emails/welcome_user.html
@@ -14,7 +14,7 @@
-
+
diff --git a/datacenterlight/templates/datacenterlight/includes/_calculator_form.html b/datacenterlight/templates/datacenterlight/includes/_calculator_form.html
index f38150bb..f9896f17 100644
--- a/datacenterlight/templates/datacenterlight/includes/_calculator_form.html
+++ b/datacenterlight/templates/datacenterlight/includes/_calculator_form.html
@@ -1,14 +1,35 @@
{% load staticfiles i18n%}
-
diff --git a/datacenterlight/templates/datacenterlight/includes/_footer.html b/datacenterlight/templates/datacenterlight/includes/_footer.html
index 4a2d8786..c4950807 100644
--- a/datacenterlight/templates/datacenterlight/includes/_footer.html
+++ b/datacenterlight/templates/datacenterlight/includes/_footer.html
@@ -3,42 +3,19 @@
diff --git a/datacenterlight/templates/datacenterlight/landing_payment.html b/datacenterlight/templates/datacenterlight/landing_payment.html
index 8e779576..fb6d51b0 100644
--- a/datacenterlight/templates/datacenterlight/landing_payment.html
+++ b/datacenterlight/templates/datacenterlight/landing_payment.html
@@ -1,5 +1,5 @@
{% extends "datacenterlight/base_hosting.html" %}
-{% load staticfiles bootstrap3 i18n %}
+{% load staticfiles bootstrap3 i18n cms_tags humanize %}
{% block css_extra %}
@@ -67,110 +67,102 @@
{%trans "Your Order" %}
-
-
-
-
-
- {%trans "Make a payment" %}
+
+
+ {% else %}
+ {%trans "Your Order" %}
+
+
+
+
+
+
+ {% if vm_pricing.discount_amount %}
+ {%trans "Credit Card"%}
-
- {% endif %}
- {% endfor %}
- {% for error in form.non_field_errors %}
-