diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..6b8710a7
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1 @@
+.git
diff --git a/Changelog b/Changelog
index a4840758..44c4dee4 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,37 @@
+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:
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..50b81cbb
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,18 @@
+FROM python:3.10.0-alpine3.15
+
+WORKDIR /usr/src/app
+
+RUN apk add --update --no-cache \
+ git \
+ build-base \
+ openldap-dev \
+ python3-dev \
+ libpq-dev \
+ && rm -rf /var/cache/apk/*
+
+# FIX https://github.com/python-ldap/python-ldap/issues/432
+RUN echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so
+
+COPY requirements.txt ./
+RUN pip install --no-cache-dir -r requirements.txt
+COPY ./ .
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/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po
index 1a1a2a26..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"
@@ -415,8 +415,9 @@ 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."
+msgstr ""
+"Deine MwSt-Nummer wird derzeit validiert. Die MwSt. wird angepasst, sobald "
+"die Validierung abgeschlossen ist."
msgid "Payment method"
msgstr "Bezahlmethode"
@@ -430,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"
@@ -454,42 +443,51 @@ msgstr "Preis ohne MwSt."
msgid "Pre VAT"
msgstr "Exkl. MwSt."
+msgid "VAT for"
+msgstr "MwSt für"
+
msgid "Your Price in Total"
msgstr "Dein Gesamtpreis"
+#, python-format
msgid ""
-"By clicking \"Place order\" this plan will charge your credit card account "
-"with %(total_price)s CHF/year"
+" 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 Jahr 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 %(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 %(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"
+"\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\" this plan will charge your credit card account "
-"with %(vm_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"
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 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"
@@ -608,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 ""
@@ -628,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."
@@ -671,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"
@@ -746,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..db36fde8
--- /dev/null
+++ b/datacenterlight/management/commands/check_vm_templates.py
@@ -0,0 +1,65 @@
+from django.core.management.base import BaseCommand
+from opennebula_api.models import OpenNebulaManager
+from datacenterlight.models import VMTemplate
+from membership.models import CustomUser
+
+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 = {}
+ 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)
+ for vm_template in VMTemplate.objects.all():
+ vm_name = 'test-%s' % 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)
+ 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))
+ self.stdout.write(self.style.SUCCESS("Done"))
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/tasks.py b/datacenterlight/tasks.py
index f080da90..899b506f 100644
--- a/datacenterlight/tasks.py
+++ b/datacenterlight/tasks.py
@@ -56,11 +56,6 @@ 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('username')
on_pass = user.get('pass')
@@ -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/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/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html
index 02bce0ed..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 %}
@@ -103,58 +111,61 @@
{% endif %}
-
-
-
-
-
- {% trans "Price Before VAT" %}
- {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF
-
-
-
-
-
-
-
-
-
-
-
-
{% trans "Pre VAT" %}
-
-
-
{% trans "VAT for" %} {{generic_payment_details.vat_country}} ({{generic_payment_details.vat_rate}}%)
-
+ {% if generic_payment_details.exclude_vat_calculations %}
+ {% else %}
+
@@ -267,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 %}
@@ -318,5 +330,14 @@
{%endblock%}
diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py
index 0cb18e5b..120cabbf 100644
--- a/datacenterlight/templatetags/custom_tags.py
+++ b/datacenterlight/templatetags/custom_tags.py
@@ -6,7 +6,7 @@ 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
+from hosting.models import GenericProduct, HostingOrder
from utils.hosting_utils import get_ip_addresses
logger = logging.getLogger(__name__)
@@ -63,6 +63,41 @@ 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:
+ 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 %}.