diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..a715c9d7
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,2 @@
+.git
+.env
diff --git a/.gitignore b/.gitignore
index 2d923e99..a41d813d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,3 +44,6 @@ secret-key
!.gitkeep
*.orig
.vscode/settings.json
+2024-*
+2025-*
+2023-*
diff --git a/Changelog b/Changelog
index 2c2d73d7..9e420c87 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,55 @@
+3.4: 2022-04-14
+ * 11566: Fix for ungleich.ch product section alignment
+3.2: 2021-02-07
+ * 8816: Update order confirmation text to better prepared for payment dispute
+ * supportticket#22990: Fix: can't add a deleted card
+3.1: 2021-01-11
+ * 8781: Fix error is setting a default card (MR!746)
+3.0: 2021-01-07
+ * 8393: Implement SCA for stripe payments (MR!745)
+ * 8691: Implment check_vm_templates management command (MR!744)
+2.14: 2020-12-07
+ * 8692: Create a script that fixes django db for the order after celery error (MR!743)
+2.13: 2020-12-02
+ * 8654: Fix 500 error on invoices list for the user contact+devuanhosting.com@virus.media (MR!742)
+ * 8593: Escape user's ssh key in xml-rpc call to create VM (MR!741)
+2.12.1: 2020-07-21
+ * 8307: Introduce "Exclude vat calculations" for Generic Products (MR!740)
+ * Change DE VAT rate to 16% from 19% (MR!739)
+2.12: 2020-06-23
+ * 7894: Show one time payment invoices (MR!738)
+2.11: 2020-06-11
+ * Bugfix: Correct the wrong constant name (caused payment to go thru and showing error and VMs not instantiated)
+2.10.8: 2020-06-10
+ * #8102: Refactor MAX_TIME_TO_WAIT_FOR_VM_TERMINATE to increase time to poll whether VM has been terminated or not (MR!737)
+2.10.7: 2020-05-25
+ * Bugfix: Handle VM templates deleted in OpenNebula but VM instances still existing (MR!736)
+ Notes for deployment:
+ When deploying define a UPDATED_TEMPLATES string represented dictionary value in .env
+```
+ # Represents Template Ids that were
+ # deleted and the new template Id to look for the template
+ # definition
+ UPDATED_TEMPLATES="{1: 100}"
+```
+2.10.6: 2020-03-25
+ * Bugfix: Handle Nonetype for discount's name (MR!735)
+2.10.5: 2020-03-17
+ * Introduce base price for VMs and let admins add stripe_coupon_id (MR!730)
+ Notes for deployment:
+ 1. Add env variable `VM_BASE_PRICE`
+ 2. Migrate datacenterlight app. This introduces the stripe_coupon_code field in the VMPricing.
+ 3. Create a coupon in stripe with the desired value and note down the stripe's coupon id
+ 4. Update the discount amount and set the corresponding coupon id in the admin
+2.10.3b: 2020-03-05
+ * #7773: Use username for communicating with opennebula all the time
+2.10.2b: 2020-02-25
+ * #7764: Fix uid represented as bytestring
+ * #7769: [hosting] ssh private key download feature does not work well on Firefox
+2.10.1: 2020-02-02:
+ * Changes the pricing structure of generic products into the pre vat and with vat (like that for VM)
+ * Shows product name (if exists) in the invoices list if it belongs to a generic product
+ * Small bugfixes (right alignment of price in the invoice list, show prices with 2 decimal places etc)
2.10: 2020-02-01
* Feature: Introduce new design to show VAT exclusive/VAT inclusive pricing together
* Feature: Separate VAT and discount in Stripe
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..4c1a9a66
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,32 @@
+# FROM python:3.10.0-alpine3.15
+FROM python:3.5-alpine3.12
+
+WORKDIR /usr/src/app
+
+RUN apk add --update --no-cache \
+ git \
+ build-base \
+ openldap-dev \
+ python3-dev \
+ postgresql-dev \
+ jpeg-dev \
+ libxml2-dev \
+ libxslt-dev \
+ libmemcached-dev \
+ zlib-dev \
+ && rm -rf /var/cache/apk/*
+
+## For alpine 3.15 replace postgresql-dev with libpq-dev
+
+# FIX https://github.com/python-ldap/python-ldap/issues/432
+RUN echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so
+
+COPY requirements.txt ./
+
+# Pillow seems to need LIBRARY_PATH set as follows: (see: https://github.com/python-pillow/Pillow/issues/1763#issuecomment-222383534)
+RUN LIBRARY_PATH=/lib:/usr/lib /bin/sh -c "pip install --no-cache-dir -r requirements.txt"
+
+COPY ./ .
+COPY entrypoint.sh /
+
+ENTRYPOINT ["/entrypoint.sh" ]
diff --git a/Makefile b/Makefile
index 67c0c15b..68ff014e 100644
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,12 @@ help:
@echo ' make rsync_upload '
@echo ' make install_debian_packages '
+buildimage:
+ docker build -t dynamicweb:$$(git describe) .
+
+releaseimage: buildimage
+ ./release.sh
+
collectstatic:
$(PY?) $(BASEDIR)/manage.py collectstatic
diff --git a/alplora/views.py b/alplora/views.py
index 0a10b4e0..d628cffc 100644
--- a/alplora/views.py
+++ b/alplora/views.py
@@ -31,9 +31,10 @@ class ContactView(FormView):
return context
def form_valid(self, form):
- form.save()
- form.send_email(email_to='info@alplora.ch')
- messages.add_message(self.request, messages.SUCCESS, self.success_message)
+ print("alplora contactusform")
+ #form.save()
+ #form.send_email(email_to='info@alplora.ch')
+ #messages.add_message(self.request, messages.SUCCESS, self.success_message)
return render(self.request, 'alplora/contact_success.html', {})
diff --git a/build-image.sh b/build-image.sh
new file mode 100755
index 00000000..64a67fd6
--- /dev/null
+++ b/build-image.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+if [ $# -lt 1 ]; then
+ echo "$0 imageversion [push]"
+ echo "Version could be: $(git describe --always)"
+ echo "If push is specified, also push to our harbor"
+ exit 1
+fi
+
+tagprefix=harbor.k8s.ungleich.ch/ungleich-public/dynamicweb
+version=$1; shift
+
+tag=${tagprefix}:${version}
+
+set -ex
+
+docker build -t "${tag}" .
+
+push=$1; shift
+
+if [ "$push" ]; then
+ docker push "${tag}"
+fi
diff --git a/datacenterlight/cms_plugins.py b/datacenterlight/cms_plugins.py
index c3ec974f..52b4f19f 100644
--- a/datacenterlight/cms_plugins.py
+++ b/datacenterlight/cms_plugins.py
@@ -1,5 +1,6 @@
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
+from django.conf import settings
from .cms_models import (
DCLBannerItemPluginModel, DCLBannerListPluginModel, DCLContactPluginModel,
@@ -100,6 +101,7 @@ class DCLCalculatorPlugin(CMSPluginBase):
vm_type=instance.vm_type
).order_by('name')
context['instance'] = instance
+ context['vm_base_price'] = settings.VM_BASE_PRICE
context['min_ram'] = 0.5 if instance.enable_512mb_ram else 1
return context
diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po
index 5ba12558..cd7fab99 100644
--- a/datacenterlight/locale/de/LC_MESSAGES/django.po
+++ b/datacenterlight/locale/de/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-02-01 09:42+0000\n"
+"POT-Creation-Date: 2021-02-07 11:10+0000\n"
"PO-Revision-Date: 2018-03-30 23:22+0000\n"
"Last-Translator: b'Anonymous User '\n"
"Language-Team: LANGUAGE \n"
@@ -144,8 +144,8 @@ msgid ""
"the heart of Switzerland."
msgstr "Bei uns findest Du die günstiges VMs aus der Schweiz."
-msgid "Try now, order a VM. VM price starts from only 10.5 CHF per month."
-msgstr "Unser Angebot beginnt bei 10.5 CHF pro Monat. Probier's jetzt aus!"
+msgid "Try now, order a VM. VM price starts from only 11.5 CHF per month."
+msgstr "Unser Angebot beginnt bei 11.5 CHF pro Monat. Probier's jetzt aus!"
msgid "ORDER VM"
msgstr "VM BESTELLEN"
@@ -407,15 +407,17 @@ msgid "Billed to"
msgstr "Rechnungsadresse"
msgid "VAT Number"
-msgstr "Mehrwertsteuernummer"
+msgstr "MwSt-Nummer"
msgid "Your VAT number has been verified"
-msgstr "Deine Mehrwertsteuernummer wurde überprüft"
+msgstr "Deine MwSt-Nummer wurde überprüft"
msgid ""
"Your VAT number is under validation. VAT will be adjusted, once the "
"validation is complete."
msgstr ""
+"Deine MwSt-Nummer wird derzeit validiert. Die MwSt. wird angepasst, sobald "
+"die Validierung abgeschlossen ist."
msgid "Payment method"
msgstr "Bezahlmethode"
@@ -429,18 +431,6 @@ msgstr "Bestellungsübersicht"
msgid "Product"
msgstr "Produkt"
-msgid "Price"
-msgstr "Preise"
-
-msgid "VAT for"
-msgstr "MwSt für"
-
-msgid "Total Amount"
-msgstr "Gesamtsumme"
-
-msgid "Amount"
-msgstr "Betrag"
-
msgid "Description"
msgstr "Beschreibung"
@@ -448,51 +438,56 @@ msgid "Recurring"
msgstr "Wiederholend"
msgid "Price Before VAT"
-msgstr ""
+msgstr "Preis ohne MwSt."
msgid "Pre VAT"
-msgstr ""
+msgstr "Exkl. MwSt."
+
+msgid "VAT for"
+msgstr "MwSt für"
msgid "Your Price in Total"
-msgstr ""
-
-#, fuzzy, python-format
-#| msgid ""
-#| "By clicking \"Place order\" this plan will charge your credit card "
-#| "account with %(total_price)s CHF/month"
-msgid ""
-"By clicking \"Place order\" this plan will charge your credit card account "
-"with %(total_price)s CHF/year"
-msgstr ""
-"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s "
-"CHF pro Jahr belastet"
+msgstr "Dein Gesamtpreis"
#, python-format
msgid ""
-"By clicking \"Place order\" this plan will charge your credit card account "
-"with %(total_price)s CHF/month"
+" By clicking \"Place order\" you agree to our Terms of Service and "
+"this plan will charge your credit card account with %(total_price)s CHF/year"
msgstr ""
-"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s "
-"CHF pro Monat belastet"
-
-#, fuzzy, python-format
-#| msgid ""
-#| "By clicking \"Place order\" this payment will charge your credit card "
-#| "account with a one time amount of %(total_price)s CHF"
-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"
+"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(total_price)s CHF/Jahr belastet."
#, python-format
msgid ""
-"By clicking \"Place order\" this plan will charge your credit card account "
-"with %(vm_total_price)s CHF/month"
+"\n"
+" By clicking \"Place order\" you agree to "
+"our Terms "
+"of Service and this plan will charge your credit card account with "
+"%(total_price)s CHF/month"
msgstr ""
-"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
-"%(vm_total_price)s CHF pro Monat belastet"
+"\n"
+"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(total_price)s CHF/Monat belastet."
+
+#, python-format
+msgid ""
+"By clicking \"Place order\" you agree to our Terms of Service and "
+"this plan will charge your credit card account with %(total_price)s CHF"
+msgstr ""
+"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(total_price)s CHF belastet."
+
+#, python-format
+msgid ""
+"By clicking \"Place order\" you agree to our Terms of Service and "
+"this plan will charge your credit card account with %(vm_total_price)s CHF/"
+"month"
+msgstr ""
+"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(vm_total_price)s CHF/Monat belastet"
msgid "Place order"
msgstr "Bestellen"
@@ -592,7 +587,7 @@ msgid "Actions speak louder than words. Let's do it, try our VM now."
msgstr "Taten sagen mehr als Worte – Teste jetzt unsere VM!"
msgid "See Invoice"
-msgstr ""
+msgstr "Siehe Rechnung"
msgid "Invalid number of cores"
msgstr "Ungültige Anzahle CPU-Kerne"
@@ -611,16 +606,22 @@ msgid "Incorrect pricing name. Please contact support{support_email}"
msgstr ""
"Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}"
-#, 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"
+#, fuzzy
+#| msgid "Thank you!"
+msgid "Thank you !"
+msgstr "Vielen Dank!"
+
+msgid "Your product will be provisioned as soon as we receive the payment."
+msgstr ""
+
+#, 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 "Error."
msgstr ""
@@ -631,10 +632,21 @@ 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}"
+msgid "Thank you for the order."
+msgstr "Danke für Deine Bestellung."
+
+msgid ""
+"Your product will be provisioned as soon as we receive a payment "
+"confirmation from Stripe. We will send you a confirmation email. You can "
+"always contact us at support@datacenterlight.ch"
msgstr ""
-"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
+
+msgid ""
+"Your VM will be up and running in a few moments. We will send you a "
+"confirmation email as soon as it is ready."
+msgstr ""
+"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
+"auf sie zugreifen kannst."
msgid " This is a monthly recurring plan."
msgstr "Dies ist ein monatlich wiederkehrender Plan."
@@ -674,15 +686,40 @@ msgstr ""
"Du wirst bald eine Bestätigungs-E-Mail über die Zahlung erhalten. Du kannst "
"jederzeit unter info@ungleich.ch kontaktieren."
-msgid "Thank you for the order."
-msgstr "Danke für Deine Bestellung."
+#, python-format
+#~ msgid ""
+#~ "By clicking \"Place order\" this plan will charge your credit card "
+#~ "account with %(total_price)s CHF/month"
+#~ msgstr ""
+#~ "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
+#~ "%(total_price)s CHF pro Monat belastet"
-msgid ""
-"Your VM will be up and running in a few moments. We will send you a "
-"confirmation email as soon as it is ready."
-msgstr ""
-"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
-"auf sie zugreifen kannst."
+#, fuzzy, python-format
+#~| msgid ""
+#~| "By clicking \"Place order\" this payment will charge your credit card "
+#~| "account with a one time amount of %(total_price)s CHF"
+#~ 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-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 "Price"
+#~ msgstr "Preise"
+
+#~ msgid "Total Amount"
+#~ msgstr "Gesamtsumme"
+
+#~ msgid "Amount"
+#~ msgstr "Betrag"
#~ msgid "Subtotal"
#~ msgstr "Zwischensumme"
@@ -749,9 +786,6 @@ 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 "
diff --git a/datacenterlight/management/commands/check_vm_templates.py b/datacenterlight/management/commands/check_vm_templates.py
new file mode 100644
index 00000000..d30e5b0d
--- /dev/null
+++ b/datacenterlight/management/commands/check_vm_templates.py
@@ -0,0 +1,80 @@
+from django.core.management.base import BaseCommand
+from opennebula_api.models import OpenNebulaManager
+from datacenterlight.models import VMTemplate
+from datacenterlight.cms_models import DCLCalculatorPluginModel
+from membership.models import CustomUser
+from utils.tasks import send_plain_email_task
+from django.conf import settings
+from time import sleep
+import datetime
+import json
+import logging
+import os
+
+logger = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ help = '''Checks all VM templates to find if they can be instantiated'''
+
+ def add_arguments(self, parser):
+ parser.add_argument('user_email', type=str)
+
+ def handle(self, *args, **options):
+ result_dict = {}
+ error_dict = {}
+ user_email = options['user_email'] if 'user_email' in options else ""
+
+ if user_email:
+ cu = CustomUser.objects.get(email=user_email)
+ specs = {'cpu': 1, 'memory': 1, 'disk_size': 10}
+ manager = OpenNebulaManager(email=user_email, password=cu.password)
+ pub_keys = [settings.TEST_MANAGE_SSH_KEY_PUBKEY]
+ PROJECT_PATH = os.path.abspath(os.path.dirname(__name__))
+ if not os.path.exists("%s/outputs" % PROJECT_PATH):
+ os.mkdir("%s/outputs" % PROJECT_PATH)
+ public_templates_plugin = DCLCalculatorPluginModel.objects.filter(cmsplugin_ptr_id=23356).first()
+ ipv6_templates_plugin = DCLCalculatorPluginModel.objects.filter(cmsplugin_ptr_id=21943).first()
+ templates = public_templates_plugin.vm_templates_to_show + ipv6_templates_plugin.vm_templates_to_show
+ vm_templates = VMTemplate.objects.filter(opennebula_vm_template_id__in=templates)
+ for vm_template in vm_templates:
+ vm_name = 'test-%s-%s' % (vm_template.vm_type, vm_template.name)
+ vm_id = manager.create_vm(
+ template_id=vm_template.opennebula_vm_template_id,
+ specs=specs,
+ ssh_key='\n'.join(pub_keys),
+ vm_name=vm_name
+ )
+ if vm_id and vm_id > 0:
+ result_dict[vm_name] = "%s OK, created VM %s" % (
+ '%s %s %s' % (vm_template.opennebula_vm_template_id,
+ vm_template.name, vm_template.vm_type),
+ vm_id
+ )
+ self.stdout.write(self.style.SUCCESS(result_dict[vm_name]))
+ manager.delete_vm(vm_id)
+ else:
+ result_dict[vm_name] = '''Error creating VM %s, template_id
+ %s %s''' % (vm_name,
+ vm_template.opennebula_vm_template_id,
+ vm_template.vm_type)
+ error_dict[vm_name] = result_dict[vm_name]
+ self.stdout.write(self.style.ERROR(result_dict[vm_name]))
+ sleep(1)
+ date_str = datetime.datetime.strftime(
+ datetime.datetime.now(), '%Y%m%d%H%M%S'
+ )
+ with open("%s/outputs/check_vm_templates_%s.txt" %
+ (PROJECT_PATH, date_str),
+ 'w',
+ encoding='utf-8') as f:
+ f.write(json.dumps(result_dict))
+ if error_dict:
+ email_data = {
+ 'subject': 'Check VM Templates ERROR',
+ 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
+ 'to': [settings.ADMIN_EMAIL],
+ 'body': json.dumps(error_dict),
+ }
+ send_plain_email_task.delay(email_data)
+ self.stdout.write(self.style.SUCCESS("Done"))
diff --git a/datacenterlight/management/commands/fix_generic_stripe_plan_product_names.py b/datacenterlight/management/commands/fix_generic_stripe_plan_product_names.py
new file mode 100644
index 00000000..2773009d
--- /dev/null
+++ b/datacenterlight/management/commands/fix_generic_stripe_plan_product_names.py
@@ -0,0 +1,54 @@
+from django.core.management.base import BaseCommand
+from datacenterlight.tasks import handle_metadata_and_emails
+from datacenterlight.models import StripePlan
+from opennebula_api.models import OpenNebulaManager
+from membership.models import CustomUser
+from hosting.models import GenericProduct
+import logging
+import json
+import sys
+import stripe
+
+logger = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ help = '''Stripe plans created before version 3.4 saved the plan name like generic-{subscription_id}-amount. This
+ command aims at replacing this with the actual product name
+ '''
+
+ def handle(self, *args, **options):
+ cnt = 0
+ self.stdout.write(
+ self.style.SUCCESS(
+ 'In Fix generic stripe plan product names'
+ )
+ )
+ plans_to_change = StripePlan.objects.filter(stripe_plan_id__startswith='generic')
+ for plan in plans_to_change:
+ response = input("Press 'y' to continue: ")
+
+ # Check if the user entered 'y'
+ if response.lower() == 'y':
+ plan_name = plan.stripe_plan_id
+ first_index_hyphen = plan_name.index("-") + 1
+ product_id = plan_name[
+ first_index_hyphen:(plan_name[first_index_hyphen:].index("-")) + first_index_hyphen]
+ gp = GenericProduct.objects.get(id=product_id)
+ if gp:
+ cnt += 1
+ # update stripe
+ sp = stripe.Plan.retrieve(plan_name)
+ pr = stripe.Product.retrieve(sp.product)
+ pr.name = gp.product_name
+ pr.save()
+ # update local
+ spl = StripePlan.objects.get(stripe_plan_id=plan_name)
+ spl.stripe_plan_name = gp.product_name
+ spl.save()
+ print("%s. %s => %s" % (cnt, plan_name, gp.product_name))
+ else:
+ print("Invalid input. Please try again.")
+ sys.exit()
+
+ print("Done")
\ No newline at end of file
diff --git a/datacenterlight/management/commands/fix_vm_after_celery_error.py b/datacenterlight/management/commands/fix_vm_after_celery_error.py
new file mode 100644
index 00000000..0cfdb423
--- /dev/null
+++ b/datacenterlight/management/commands/fix_vm_after_celery_error.py
@@ -0,0 +1,76 @@
+from django.core.management.base import BaseCommand
+from datacenterlight.tasks import handle_metadata_and_emails
+from opennebula_api.models import OpenNebulaManager
+from membership.models import CustomUser
+import logging
+import json
+
+logger = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ help = '''Updates the DB after manual creation of VM'''
+
+ def add_arguments(self, parser):
+ parser.add_argument('vm_id', type=int)
+ parser.add_argument('order_id', type=int)
+ parser.add_argument('user', type=str)
+ parser.add_argument('specs', type=str)
+ parser.add_argument('template', type=str)
+
+ def handle(self, *args, **options):
+ vm_id = options['vm_id']
+ order_id = options['order_id']
+ user_str = options['user']
+ specs_str = options['specs']
+ template_str = options['template']
+
+ json_acceptable_string = user_str.replace("'", "\"")
+ user_dict = json.loads(json_acceptable_string)
+
+ json_acceptable_string = specs_str.replace("'", "\"")
+ specs = json.loads(json_acceptable_string)
+
+ json_acceptable_string = template_str.replace("'", "\"")
+ template = json.loads(json_acceptable_string)
+ if vm_id <= 0:
+ self.stdout.write(self.style.ERROR(
+ 'vm_id can\'t be less than or 0. Given: %s' % vm_id))
+ return
+ if vm_id <= 0:
+ self.stdout.write(self.style.ERROR(
+ 'order_id can\'t be less than or 0. Given: %s' % vm_id))
+ return
+ if specs_str is None or specs_str == "":
+ self.stdout.write(
+ self.style.ERROR('specs can\'t be empty or None'))
+ return
+
+ user = {
+ 'name': user_dict['name'],
+ 'email': user_dict['email'],
+ 'username': user_dict['username'],
+ 'pass': user_dict['pass'],
+ 'request_scheme': user_dict['request_scheme'],
+ 'request_host': user_dict['request_host'],
+ 'language': user_dict['language'],
+ }
+ cu = CustomUser.objects.get(username=user.get('username'))
+ # Create OpenNebulaManager
+ self.stdout.write(
+ self.style.SUCCESS(
+ 'Connecting using %s' % (cu.username)
+ )
+ )
+ manager = OpenNebulaManager(email=cu.username, password=cu.password)
+ handle_metadata_and_emails(order_id, vm_id, manager, user, specs,
+ template)
+ self.stdout.write(
+ self.style.SUCCESS(
+ 'Done handling metadata and emails for %s %s %s' % (
+ order_id,
+ vm_id,
+ str(user)
+ )
+ )
+ )
diff --git a/datacenterlight/migrations/0031_vmpricing_stripe_coupon_id.py b/datacenterlight/migrations/0031_vmpricing_stripe_coupon_id.py
new file mode 100644
index 00000000..d2e45871
--- /dev/null
+++ b/datacenterlight/migrations/0031_vmpricing_stripe_coupon_id.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2020-02-04 03:16
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('datacenterlight', '0030_dclnavbarpluginmodel_show_non_transparent_navbar_always'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='vmpricing',
+ name='stripe_coupon_id',
+ field=models.CharField(blank=True, max_length=255, null=True),
+ ),
+ ]
diff --git a/datacenterlight/models.py b/datacenterlight/models.py
index 6410254b..64d785a2 100644
--- a/datacenterlight/models.py
+++ b/datacenterlight/models.py
@@ -54,6 +54,7 @@ class VMPricing(models.Model):
discount_amount = models.DecimalField(
max_digits=6, decimal_places=2, default=0
)
+ stripe_coupon_id = models.CharField(max_length=255, null=True, blank=True)
def __str__(self):
display_str = self.name + ' => ' + ' - '.join([
diff --git a/datacenterlight/static/datacenterlight/js/main.js b/datacenterlight/static/datacenterlight/js/main.js
index 8fea438a..c6869cda 100644
--- a/datacenterlight/static/datacenterlight/js/main.js
+++ b/datacenterlight/static/datacenterlight/js/main.js
@@ -225,8 +225,8 @@
}
var total = (cardPricing['cpu'].value * window.coresUnitPrice) +
(cardPricing['ram'].value * window.ramUnitPrice) +
- (cardPricing['storage'].value * window.ssdUnitPrice) -
- window.discountAmount;
+ (cardPricing['storage'].value * window.ssdUnitPrice) +
+ window.vmBasePrice - window.discountAmount;
total = parseFloat(total.toFixed(2));
$("#total").text(total);
}
diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py
index 55be8099..899b506f 100644
--- a/datacenterlight/tasks.py
+++ b/datacenterlight/tasks.py
@@ -56,13 +56,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
"Running create_vm_task on {}".format(current_task.request.hostname))
vm_id = None
try:
- final_price = (
- specs.get('total_price') if 'total_price' in specs
- else specs.get('price')
- )
-
if 'pass' in user:
- on_user = user.get('email')
+ on_user = user.get('username')
on_pass = user.get('pass')
logger.debug("Using user {user} to create VM".format(user=on_user))
vm_name = None
@@ -92,107 +87,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
if vm_id is None:
raise Exception("Could not create VM")
- # 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)}
- )
-
- 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
-
- context = {
- 'name': user.get('name'),
- 'email': user.get('email'),
- 'cores': specs.get('cpu'),
- 'memory': specs.get('memory'),
- 'storage': specs.get('disk_size'),
- 'price': final_price,
- 'template': template.get('name'),
- 'vm_name': vm.get('name'),
- 'vm_id': vm['vm_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,
- 'to': ['info@ungleich.ch'],
- 'body': "\n".join(
- ["%s=%s" % (k, v) for (k, v) in context.items()]),
- 'reply_to': [context['email']],
- }
- email = EmailMessage(**email_data)
- email.send()
-
- if 'pass' in user:
- lang = 'en-us'
- if user.get('language') is not None:
- logger.debug(
- "Language is set to {}".format(user.get('language')))
- lang = user.get('language')
- translation.activate(lang)
- # Send notification to the user as soon as VM has been booked
- context = {
- 'base_url': "{0}://{1}".format(user.get('request_scheme'),
- user.get('request_host')),
- 'order_url': reverse('hosting:invoices'),
- 'page_header': _(
- 'Your New VM %(vm_name)s at Data Center Light') % {
- 'vm_name': vm.get('name')},
- 'vm_name': vm.get('name')
- }
- email_data = {
- 'subject': context.get('page_header'),
- 'to': user.get('email'),
- 'context': context,
- 'template_name': 'new_booked_vm',
- 'template_path': 'hosting/emails/',
- 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS,
- }
- email = BaseEmail(**email_data)
- email.send()
-
- logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
- if vm_id > 0:
- get_or_create_vm_detail(custom_user, manager, vm_id)
+ handle_metadata_and_emails(order_id, vm_id, manager, user, specs,
+ template)
except Exception as e:
logger.error(str(e))
try:
@@ -214,3 +110,127 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
return
return vm_id
+
+
+def handle_metadata_and_emails(order_id, vm_id, manager, user, specs,
+ template):
+ """
+ Handle's setting up of the metadata in Stripe and database and sending of
+ emails to the user after VM creation
+
+ :param order_id: the hosting order id
+ :param vm_id: the id of the vm created
+ :param manager: the OpenNebula Manager instance
+ :param user: the user's dict passed to the celery task
+ :param specs: the specification's dict passed to the celery task
+ :param template: the template dict passed to the celery task
+
+ :return:
+ """
+
+ custom_user = CustomUser.objects.get(email=user.get('email'))
+ final_price = (
+ specs.get('total_price') if 'total_price' in specs
+ else specs.get('price')
+ )
+ # 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)}
+ )
+
+ 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
+
+ context = {
+ 'name': user.get('name'),
+ 'email': user.get('email'),
+ 'cores': specs.get('cpu'),
+ 'memory': specs.get('memory'),
+ 'storage': specs.get('disk_size'),
+ 'price': final_price,
+ 'template': template.get('name'),
+ 'vm_name': vm.get('name'),
+ 'vm_id': vm['vm_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,
+ 'to': ['dcl-orders@ungleich.ch'],
+ 'body': "\n".join(
+ ["%s=%s" % (k, v) for (k, v) in context.items()]),
+ 'reply_to': [context['email']],
+ }
+ email = EmailMessage(**email_data)
+ email.send()
+
+ if 'pass' in user:
+ lang = 'en-us'
+ if user.get('language') is not None:
+ logger.debug(
+ "Language is set to {}".format(user.get('language')))
+ lang = user.get('language')
+ translation.activate(lang)
+ # Send notification to the user as soon as VM has been booked
+ context = {
+ 'base_url': "{0}://{1}".format(user.get('request_scheme'),
+ user.get('request_host')),
+ 'order_url': reverse('hosting:invoices'),
+ 'page_header': _(
+ 'Your New VM %(vm_name)s at Data Center Light') % {
+ 'vm_name': vm.get('name')},
+ 'vm_name': vm.get('name')
+ }
+ email_data = {
+ 'subject': context.get('page_header'),
+ 'to': user.get('email'),
+ 'context': context,
+ 'template_name': 'new_booked_vm',
+ 'template_path': 'hosting/emails/',
+ 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS,
+ }
+ email = BaseEmail(**email_data)
+ email.send()
+
+ logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
+ if vm_id > 0:
+ get_or_create_vm_detail(custom_user, manager, vm_id)
diff --git a/datacenterlight/templates/datacenterlight/cms/calculator.html b/datacenterlight/templates/datacenterlight/cms/calculator.html
index 7b123a72..20a6664a 100644
--- a/datacenterlight/templates/datacenterlight/cms/calculator.html
+++ b/datacenterlight/templates/datacenterlight/cms/calculator.html
@@ -1,5 +1,5 @@
- {% include "datacenterlight/includes/_calculator_form.html" with vm_pricing=instance.pricing %}
+ {% include "datacenterlight/includes/_calculator_form.html" with vm_pricing=instance.pricing vm_base_price=vm_base_price %}
\ No newline at end of file
diff --git a/datacenterlight/templates/datacenterlight/emails/welcome_user.html b/datacenterlight/templates/datacenterlight/emails/welcome_user.html
index 25185618..2044b2ee 100644
--- a/datacenterlight/templates/datacenterlight/emails/welcome_user.html
+++ b/datacenterlight/templates/datacenterlight/emails/welcome_user.html
@@ -28,7 +28,7 @@
{% blocktrans %}Thanks for joining us! We provide the most affordable virtual machines from the heart of Switzerland.{% endblocktrans %}
- {% blocktrans %}Try now, order a VM. VM price starts from only 10.5 CHF per month.{% endblocktrans %}
+ {% blocktrans %}Try now, order a VM. VM price starts from only 11.5 CHF per month.{% endblocktrans %}
diff --git a/datacenterlight/templates/datacenterlight/emails/welcome_user.txt b/datacenterlight/templates/datacenterlight/emails/welcome_user.txt
index 772e51a5..06e8aa33 100644
--- a/datacenterlight/templates/datacenterlight/emails/welcome_user.txt
+++ b/datacenterlight/templates/datacenterlight/emails/welcome_user.txt
@@ -3,7 +3,7 @@
{% trans "Welcome to Data Center Light!" %}
{% blocktrans %}Thanks for joining us! We provide the most affordable virtual machines from the heart of Switzerland.{% endblocktrans %}
-{% blocktrans %}Try now, order a VM. VM price starts from only 10.5 CHF per month.{% endblocktrans %}
+{% blocktrans %}Try now, order a VM. VM price starts from only 11.5 CHF per month.{% endblocktrans %}
{{ base_url }}{% url 'hosting:create_virtual_machine' %}
diff --git a/datacenterlight/templates/datacenterlight/includes/_calculator_form.html b/datacenterlight/templates/datacenterlight/includes/_calculator_form.html
index f64a9500..2c2b51dd 100644
--- a/datacenterlight/templates/datacenterlight/includes/_calculator_form.html
+++ b/datacenterlight/templates/datacenterlight/includes/_calculator_form.html
@@ -9,6 +9,7 @@
window.ssdUnitPrice = {{vm_pricing.ssd_unit_price|default:0}};
window.hddUnitPrice = {{vm_pricing.hdd_unit_price|default:0}};
window.discountAmount = {{vm_pricing.discount_amount|default:0}};
+ window.vmBasePrice = {{vm_base_price|default:0}};
window.minRam = {{min_ram}};
window.minRamErr = '{% blocktrans with min_ram=min_ram %}Please enter a value in range {{min_ram}} - 200.{% endblocktrans %}';
diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html
index 6f9d43f2..1f7a3cda 100644
--- a/datacenterlight/templates/datacenterlight/order_detail.html
+++ b/datacenterlight/templates/datacenterlight/order_detail.html
@@ -2,6 +2,14 @@
{% load staticfiles bootstrap3 i18n custom_tags humanize %}
{% block content %}
+
{% if messages %}
@@ -58,55 +66,15 @@
{% trans "Order summary" %}
- {% if generic_payment_details %}
-
- {% trans "Product" %}:
- {{ generic_payment_details.product_name }}
-
-
-
- {% if generic_payment_details.vat_rate > 0 %}
-
- {% trans "Price" %}:
- CHF {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}}
-
{% trans "Your Price in Total" %}{{vm.total_price|floatformat:2|intcomma}} CHF
@@ -222,15 +278,16 @@
{% if generic_payment_details %}
{% if generic_payment_details.recurring %}
{% if generic_payment_details.recurring_interval == 'year' %}
-
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/year{% endblocktrans %}.
+
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %} By clicking "Place order" you agree to our Terms of Service and this plan will charge your credit card account with {{ total_price }} CHF/year{% endblocktrans %}.
{% else %}
-
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/month{% endblocktrans %}.
+
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}
+ By clicking "Place order" you agree to our Terms of Service and this plan will charge your credit card account with {{ total_price }} CHF/month{% endblocktrans %}.
{% endif %}
{% else %}
-
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this payment will charge your credit card account with a one time amount of {{total_price}} CHF{% endblocktrans %}.
+
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" you agree to our Terms of Service and this plan will charge your credit card account with {{ total_price }} CHF{% endblocktrans %}.
{% endif %}
{% else %}
-
{% blocktrans with vm_total_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{vm_total_price}} CHF/month{% endblocktrans %}.
+
{% blocktrans with vm_total_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" you agree to our Terms of Service and this plan will charge your credit card account with {{ vm_total_price }} CHF/month{% endblocktrans %}.
{% endif %}
@@ -273,5 +330,14 @@
{%endblock%}
diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py
index 4cc50caa..fc39eec7 100644
--- a/datacenterlight/templatetags/custom_tags.py
+++ b/datacenterlight/templatetags/custom_tags.py
@@ -1,12 +1,16 @@
import datetime
+import logging
from django import template
from django.core.urlresolvers import resolve, reverse
from django.utils.safestring import mark_safe
from django.utils.translation import activate, get_language, ugettext_lazy as _
+from hosting.models import GenericProduct, HostingOrder
from utils.hosting_utils import get_ip_addresses
+logger = logging.getLogger(__name__)
+
register = template.Library()
@@ -59,6 +63,42 @@ def escaped_line_break(value):
return value.replace("\\n", "\n")
+@register.filter('get_line_item_from_hosting_order_charge')
+def get_line_item_from_hosting_order_charge(hosting_order_id):
+ """
+ Returns ready-to-use "html" line item to be shown for a charge in the
+ invoice list page
+
+ :param hosting_order_id: the HostingOrder id
+ :return:
+ """
+ try:
+ print("Hositng order id = %s" % hosting_order_id)
+ hosting_order = HostingOrder.objects.get(id = hosting_order_id)
+ if hosting_order.stripe_charge_id:
+ return mark_safe("""
+
{% blocktrans with vm_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{ vm_price }} CHF/month{% endblocktrans %}.
+
{% blocktrans with vm_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" you agree to our Terms of Service and this plan will charge your credit card account with {{ vm_price }} CHF/month.{% endblocktrans %}.
{{order.price|floatformat:2|intcomma}} CHF/{% if order.generic_product %}{% trans order.generic_product.product_subscription_interval %}{% else %}{% trans "Month" %}{% endif %}