From 249769f7322e18d5fe77cfa4e9a8eb29cf5225be Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 27 Nov 2019 12:54:58 +0530 Subject: [PATCH 001/471] Add generic header plugin & model --- datacenterlight/cms_models.py | 20 ++++++++++++++++++++ datacenterlight/cms_plugins.py | 25 ++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/datacenterlight/cms_models.py b/datacenterlight/cms_models.py index 2d1a98b5..99940d03 100644 --- a/datacenterlight/cms_models.py +++ b/datacenterlight/cms_models.py @@ -13,6 +13,7 @@ from filer.fields.file import FilerFileField from filer.fields.image import FilerImageField from datacenterlight.models import VMPricing, VMTemplate +from hosting.models import GenericProduct class CMSIntegration(models.Model): @@ -362,3 +363,22 @@ class DCLCalculatorPluginModel(CMSPlugin): " default when the calculator loads" ) enable_512mb_ram = models.BooleanField(default=False) + + +class GenericProductHeaderPluginModel(CMSPlugin): + generic_product = models.ForeignKey( + GenericProduct, + related_name="dcl_generic_product_header_plugin", + help_text='Choose or create a generic product that you want to link ' + 'with this header', + null=True, + blank=True + ) + header_text = models.TextField( + help_text="Give a header text" + ) + header_background_color = models.CharField(default="3c4e68", max_length=10) + buy_button_text = models.TextField( + default="Buy now", + help_text="Input the text to be shown on the buy button" + ) \ No newline at end of file diff --git a/datacenterlight/cms_plugins.py b/datacenterlight/cms_plugins.py index c3ec974f..7d503e9e 100644 --- a/datacenterlight/cms_plugins.py +++ b/datacenterlight/cms_plugins.py @@ -1,3 +1,5 @@ +from django.core.urlresolvers import reverse + from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool @@ -6,7 +8,8 @@ from .cms_models import ( DCLFooterPluginModel, DCLLinkPluginModel, DCLNavbarDropdownPluginModel, DCLSectionIconPluginModel, DCLSectionImagePluginModel, DCLSectionPluginModel, DCLNavbarPluginModel, - DCLSectionPromoPluginModel, DCLCalculatorPluginModel + DCLSectionPromoPluginModel, DCLCalculatorPluginModel, + GenericProductHeaderPluginModel ) from .models import VMTemplate from datacenterlight.utils import clear_all_session_vars @@ -178,3 +181,23 @@ class DCLFooterPlugin(CMSPluginBase): cache = False allow_children = True child_classes = ['DCLLinkPlugin'] + + +@plugin_pool.register_plugin +class GenericProductHeaderPlugin(CMSPluginBase): + module = "Datacenterlight" + name = "Generic Product Header Plugin" + model = GenericProductHeaderPluginModel + render_template = "datacenterlight/cms/generic_product_header.html" + cache = False + + def render(self, context, instance, placeholder): + product_url = None + if instance.generic_product: + product_url = reverse( + 'show_product', + kwargs={'product_slug': instance.generic_product.product_slug} + ) + context["product_url"] = product_url + context["instance"] = instance + return context \ No newline at end of file From 2b8bf3f1f223550cb7a16218ebd147bad04c36d7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 27 Nov 2019 12:55:19 +0530 Subject: [PATCH 002/471] Add migration --- .../0030_genericproductheaderpluginmodel.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 datacenterlight/migrations/0030_genericproductheaderpluginmodel.py diff --git a/datacenterlight/migrations/0030_genericproductheaderpluginmodel.py b/datacenterlight/migrations/0030_genericproductheaderpluginmodel.py new file mode 100644 index 00000000..fd7cb339 --- /dev/null +++ b/datacenterlight/migrations/0030_genericproductheaderpluginmodel.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-11-27 07:18 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0058_genericproduct_product_subscription_interval'), + ('cms', '0014_auto_20160404_1908'), + ('datacenterlight', '0029_auto_20190420_1022'), + ] + + operations = [ + migrations.CreateModel( + name='GenericProductHeaderPluginModel', + 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')), + ('header_text', models.TextField(help_text='Give a header text')), + ('header_background_color', models.CharField(default='3c4e68', max_length=10)), + ('buy_button_text', models.TextField(default='Buy now', help_text='Input the text to be shown on the buy button')), + ('generic_product', models.ForeignKey(blank=True, help_text='Choose or create a generic product that you want to link with this header', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='dcl_generic_product_header_plugin', to='hosting.GenericProduct')), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + ] From 5fa0a645c1a70a451916bf6cc2bfe3eb364b7f01 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 27 Nov 2019 12:55:39 +0530 Subject: [PATCH 003/471] Add template file --- .../cms/generic_product_header.html | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 datacenterlight/templates/datacenterlight/cms/generic_product_header.html diff --git a/datacenterlight/templates/datacenterlight/cms/generic_product_header.html b/datacenterlight/templates/datacenterlight/cms/generic_product_header.html new file mode 100644 index 00000000..7d5b36b4 --- /dev/null +++ b/datacenterlight/templates/datacenterlight/cms/generic_product_header.html @@ -0,0 +1,41 @@ +{% load cms_tags %} + +
+
+
+ {{ instance.header_text }} + {% if product_url %} + + {% endif %} +
+
+
\ No newline at end of file From 5df23fb56a68cb15093833c3b6e4d813d9d1740c Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 27 Nov 2019 17:45:49 +0530 Subject: [PATCH 004/471] Fix button location and make things dynamic --- .../cms/generic_product_header.html | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/cms/generic_product_header.html b/datacenterlight/templates/datacenterlight/cms/generic_product_header.html index 7d5b36b4..5d63866a 100644 --- a/datacenterlight/templates/datacenterlight/cms/generic_product_header.html +++ b/datacenterlight/templates/datacenterlight/cms/generic_product_header.html @@ -1,18 +1,19 @@ {% load cms_tags %}
@@ -32,9 +34,7 @@
{{ instance.header_text }} {% if product_url %} - + {{instance.buy_button_text}} {% endif %}
From 9a84fc899e80faf106a1f6063839270ad21d4de4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 28 Nov 2019 12:10:40 +0530 Subject: [PATCH 005/471] Add a line separator when fetching more than 1 stripe bill --- hosting/management/commands/fetch_stripe_bills.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py index 1e4d1ab3..e2ccc53f 100644 --- a/hosting/management/commands/fetch_stripe_bills.py +++ b/hosting/management/commands/fetch_stripe_bills.py @@ -64,3 +64,6 @@ class Command(BaseCommand): 'Customer email %s does not have a stripe customer.' % email)) except Exception as e: print(" *** Error occurred. Details {}".format(str(e))) + self.stdout.write( + self.style.SUCCESS("---------------------------------------------") + ) From 987efe8f99a7e831e492152716c99dc21fdf1cbf Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 28 Nov 2019 12:35:32 +0530 Subject: [PATCH 006/471] Move separator within loop --- hosting/management/commands/fetch_stripe_bills.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py index e2ccc53f..ed26b13f 100644 --- a/hosting/management/commands/fetch_stripe_bills.py +++ b/hosting/management/commands/fetch_stripe_bills.py @@ -62,8 +62,9 @@ class Command(BaseCommand): else: self.stdout.write(self.style.SUCCESS( 'Customer email %s does not have a stripe customer.' % email)) + self.stdout.write( + self.style.SUCCESS( + "---------------------------------------------") + ) except Exception as e: print(" *** Error occurred. Details {}".format(str(e))) - self.stdout.write( - self.style.SUCCESS("---------------------------------------------") - ) From a2635e6fb903b45bf4e4665a6a4695ef57605850 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 28 Nov 2019 12:51:02 +0530 Subject: [PATCH 007/471] Update customuser add stripe import remark --- ...10_customuser_import_stripe_bill_remark.py | 20 +++++++++++++++++++ membership/models.py | 4 ++++ 2 files changed, 24 insertions(+) create mode 100644 membership/migrations/0010_customuser_import_stripe_bill_remark.py diff --git a/membership/migrations/0010_customuser_import_stripe_bill_remark.py b/membership/migrations/0010_customuser_import_stripe_bill_remark.py new file mode 100644 index 00000000..6e824e3e --- /dev/null +++ b/membership/migrations/0010_customuser_import_stripe_bill_remark.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-11-28 07:19 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0009_deleteduser'), + ] + + operations = [ + migrations.AddField( + model_name='customuser', + name='import_stripe_bill_remark', + field=models.TextField(default='', help_text='Indicates any issues while importing stripe bills'), + ), + ] diff --git a/membership/models.py b/membership/models.py index 1a622bd5..df5a5326 100644 --- a/membership/models.py +++ b/membership/models.py @@ -82,6 +82,10 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): help_text=_( 'Designates whether the user can log into this admin site.'), ) + import_stripe_bill_remark = models.TextField( + default="", + help_text="Indicates any issues while importing stripe bills" + ) objects = MyUserManager() From b683a5ac44bf5f392a9dd72f782343e1e110a912 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 28 Nov 2019 13:38:45 +0530 Subject: [PATCH 008/471] Save import remark --- .../management/commands/fetch_stripe_bills.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py index ed26b13f..b3331ddb 100644 --- a/hosting/management/commands/fetch_stripe_bills.py +++ b/hosting/management/commands/fetch_stripe_bills.py @@ -1,3 +1,4 @@ +import datetime import logging from django.core.management.base import BaseCommand @@ -19,6 +20,10 @@ class Command(BaseCommand): def handle(self, *args, **options): try: for email in options['customer_email']: + self.stdout.write( + self.style.SUCCESS( + "---------------------------------------------") + ) stripe_utils = StripeUtils() user = CustomUser.objects.get(email=email) if hasattr(user, 'stripecustomer'): @@ -39,7 +44,9 @@ class Command(BaseCommand): ) if all_invoices_response['error'] is not None: self.stdout.write(self.style.ERROR(all_invoices_response['error'])) - exit(1) + user.import_stripe_bill_remark += "{}: {},".format(datetime.datetime.now(), all_invoices_response['error']) + user.save() + continue all_invoices = all_invoices_response['response_object'] self.stdout.write(self.style.SUCCESS("Obtained {} invoices".format(len(all_invoices) if all_invoices is not None else 0))) num_invoice_created = 0 @@ -54,6 +61,10 @@ class Command(BaseCommand): if MonthlyHostingBill.create(invoice) is not None: num_invoice_created += 1 else: + user.import_stripe_bill_remark += "{}: Import failed - {},".format( + datetime.datetime.now(), + invoice['invoice_id']) + user.save() logger.error("Did not import invoice for %s" "" % str(invoice)) self.stdout.write( @@ -62,9 +73,9 @@ class Command(BaseCommand): else: self.stdout.write(self.style.SUCCESS( 'Customer email %s does not have a stripe customer.' % email)) - self.stdout.write( - self.style.SUCCESS( - "---------------------------------------------") - ) + user.import_stripe_bill_remark += "{}: No stripecustomer,".format( + datetime.datetime.now() + ) + user.save() except Exception as e: print(" *** Error occurred. Details {}".format(str(e))) From cc5d82ccac37be4752ed8b6cb0fbf113eba6dbaf Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 28 Nov 2019 13:59:03 +0530 Subject: [PATCH 009/471] Allow None value for billing_reason --- hosting/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hosting/models.py b/hosting/models.py index 6050339a..0e6caa50 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -345,7 +345,10 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model): args['period_start']).replace(tzinfo=pytz.utc), period_end=datetime.utcfromtimestamp( args['period_end']).replace(tzinfo=pytz.utc), - billing_reason=args['billing_reason'], + billing_reason=( + args['billing_reason'] + if args['billing_reason'] is not None else '' + ), discount=args['discount'], total=args['total'], lines_data_count=args['lines_data_count'], From b75947127415f76f3aea1da5c74c9d7b0f37fead Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 4 Dec 2019 01:17:46 +0530 Subject: [PATCH 010/471] Add /year text for yearly products --- hosting/templates/hosting/virtual_machine_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index ce02036f..5873a2aa 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -45,7 +45,7 @@

{% trans "Billing" %}

{% trans "Current Pricing" %}
-
{{order.price|floatformat:2|intcomma}} CHF/{% trans "Month" %}
+
{{order.price|floatformat:2|intcomma}} CHF/{% if order.generic_product %}{% trans order.generic_product.product_subscription_interval %}{% else %}{% trans "Month" %}{% endif %}
{% trans "See Invoice" %}
From 3b0e479a707cd3399435223160182511b580c9d5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 7 Dec 2019 19:26:21 +0530 Subject: [PATCH 011/471] Use country specific vats for dcl vm buy flow --- .../datacenterlight/order_detail.html | 2 +- datacenterlight/views.py | 24 +++++++++++-- utils/hosting_utils.py | 35 +++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index bc8e7562..bd2edcb2 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -120,7 +120,7 @@ {{vm.price|floatformat:2|intcomma}} CHF

- {% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) + {% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%) : {{vm.vat|floatformat:2|intcomma}} CHF

{% endif %} diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 44226abb..d4e43703 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -27,7 +27,8 @@ from utils.forms import ( BillingAddress ) from utils.hosting_utils import ( - get_vm_price_with_vat, get_all_public_keys, get_vat_rate_for_country + get_vm_price_with_vat, get_all_public_keys, get_vat_rate_for_country, + get_vm_price_for_given_vat ) from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task @@ -600,8 +601,27 @@ class OrderConfirmationView(DetailView, FormView): request.session['generic_payment_details'], }) else: + vm_specs = request.session.get('specs') + user_vat_country = ( + request.session.get('billing_address_data').get("country") + ) + user_country_vat_rate = get_vat_rate_for_country(user_vat_country) + price, vat, vat_percent, discount = get_vm_price_for_given_vat( + cpu=vm_specs['cpu'], + memory=vm_specs['memory'], + ssd_size=vm_specs['disk_size'], + pricing_name=vm_specs['pricing_name'], + vat_rate=user_country_vat_rate * 100 + ) + vm_specs["price"] = price + vm_specs["vat"] = vat + vm_specs["vat_percent"] = vat_percent + vm_specs["vat_country"] = vat_percent + vm_specs["discount"] = discount + vm_specs["total_price"] = round(price + vat - discount['amount'], 2) + context.update({ - 'vm': request.session.get('specs'), + 'vm': vm_specs, 'form': UserHostingKeyForm(request=self.request), 'keys': get_all_public_keys(self.request.user) }) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 9c0243e4..d4d27405 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -84,6 +84,41 @@ def get_vm_price(cpu, memory, disk_size, hdd_size=0, pricing_name='default'): return round(float(price), 2) +def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, + pricing_name='default', vat_rate=0): + try: + pricing = VMPricing.objects.get(name=pricing_name) + except Exception as ex: + logger.error( + "Error getting VMPricing object for {pricing_name}." + "Details: {details}".format( + pricing_name=pricing_name, details=str(ex) + ) + ) + return None + + price = ( + (decimal.Decimal(cpu) * pricing.cores_unit_price) + + (decimal.Decimal(memory) * pricing.ram_unit_price) + + (decimal.Decimal(ssd_size) * pricing.ssd_unit_price) + + (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) + ) + + vat = price * vat_rate * decimal.Decimal(0.01) + vat_percent = pricing.vat_percentage + + cents = decimal.Decimal('.01') + price = price.quantize(cents, decimal.ROUND_HALF_UP) + vat = vat.quantize(cents, decimal.ROUND_HALF_UP) + discount = { + 'name': pricing.discount_name, + 'amount': round(float(pricing.discount_amount), 2) + } + return (round(float(price), 2), round(float(vat), 2), + round(float(vat_percent), 2), discount) + + + def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0, pricing_name='default'): """ From b33271ce7d7d19a4eaa5d0489014e55e6f6b54b5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 7 Dec 2019 19:38:33 +0530 Subject: [PATCH 012/471] Make vat_rate Decimal before Decimal operations --- utils/hosting_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index d4d27405..73e2c035 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -104,8 +104,8 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) ) - vat = price * vat_rate * decimal.Decimal(0.01) - vat_percent = pricing.vat_percentage + vat = price * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) + vat_percent = vat_rate cents = decimal.Decimal('.01') price = price.quantize(cents, decimal.ROUND_HALF_UP) From d8172d6bb20be17c9ebfcfff0075c4f0b9ad4882 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 7 Dec 2019 19:42:46 +0530 Subject: [PATCH 013/471] Fix vat_country --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index d4e43703..8ed0b794 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -616,7 +616,7 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["price"] = price vm_specs["vat"] = vat vm_specs["vat_percent"] = vat_percent - vm_specs["vat_country"] = vat_percent + vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount vm_specs["total_price"] = round(price + vat - discount['amount'], 2) From d864f82e0f0cbd90c62255b05aed040dc70944c7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 9 Dec 2019 12:30:49 +0530 Subject: [PATCH 014/471] Make invoice EU VAT compatible --- dynamicweb/settings/base.py | 1 + hosting/templates/hosting/invoice_detail.html | 6 +++- hosting/templates/hosting/order_detail.html | 7 +++- hosting/views.py | 33 ++++++++++++++----- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 1051c4ab..32070ea8 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -727,6 +727,7 @@ AUTH_SEED = env('AUTH_SEED') AUTH_REALM = env('AUTH_REALM') OTP_SERVER = env('OTP_SERVER') OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT') +FIRST_VM_ID_AFTER_EU_VAT = env('FIRST_VM_ID_AFTER_EU_VAT') if DEBUG: diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html index b9a3e742..67fa06e4 100644 --- a/hosting/templates/hosting/invoice_detail.html +++ b/hosting/templates/hosting/invoice_detail.html @@ -147,8 +147,12 @@ CHF

- {% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) + {% if vm.after_eu_vat_intro %} + {% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%) : + {% else %} + {% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) + {% endif %} {{vm.vat|floatformat:2|intcomma}} CHF

{% endif %} diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index 2775882d..b725a645 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -142,7 +142,12 @@ {{vm.price|floatformat:2|intcomma}} CHF

- {% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) + {% if vm.after_eu_vat_intro %} + {% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%) : + {% else %} + {% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) + + {% endif %} {{vm.vat|floatformat:2|intcomma}} CHF

{% endif %} diff --git a/hosting/views.py b/hosting/views.py index 25303b99..c5a4a761 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -50,7 +50,10 @@ from utils.forms import ( ResendActivationEmailForm ) from utils.hosting_utils import get_all_public_keys -from utils.hosting_utils import get_vm_price_with_vat, HostingUtils +from utils.hosting_utils import ( + get_vm_price_with_vat, get_vm_price_for_given_vat, HostingUtils, + get_vat_rate_for_country +) from utils.mailer import BaseEmail from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task @@ -845,18 +848,32 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): context['vm'] = vm_detail.__dict__ context['vm']['name'] = '{}-{}'.format( context['vm']['configuration'], context['vm']['vm_id']) - price, vat, vat_percent, discount = get_vm_price_with_vat( + user_vat_country = obj.billing_address.country + user_country_vat_rate = get_vat_rate_for_country( + user_vat_country) + price, vat, vat_percent, discount = get_vm_price_for_given_vat( cpu=context['vm']['cores'], ssd_size=context['vm']['disk_size'], memory=context['vm']['memory'], pricing_name=(obj.vm_pricing.name - if obj.vm_pricing else 'default') + if obj.vm_pricing else 'default'), + vat_rate= ( + user_country_vat_rate * 100 + if obj.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT + else 7.7 + ) ) - context['vm']['vat'] = vat - context['vm']['price'] = price - context['vm']['discount'] = discount - context['vm']['vat_percent'] = vat_percent - context['vm']['total_price'] = price + vat - discount['amount'] + context['vm']["after_eu_vat_intro"] = ( + True if obj.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT + else False + ) + context['vm']["price"] = price + context['vm']["vat"] = vat + context['vm']["vat_percent"] = vat_percent + context['vm']["vat_country"] = user_vat_country + context['vm']["discount"] = discount + context['vm']["total_price"] = round( + price + vat - discount['amount'], 2) context['subscription_end_date'] = vm_detail.end_date() except VMDetail.DoesNotExist: try: From e940b468c4645b913973d2e9a25900d2f2e82eab Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 9 Dec 2019 13:24:14 +0530 Subject: [PATCH 015/471] Retrieve VM_ID as str --- dynamicweb/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 32070ea8..70d3ec2c 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -727,7 +727,7 @@ AUTH_SEED = env('AUTH_SEED') AUTH_REALM = env('AUTH_REALM') OTP_SERVER = env('OTP_SERVER') OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT') -FIRST_VM_ID_AFTER_EU_VAT = env('FIRST_VM_ID_AFTER_EU_VAT') +FIRST_VM_ID_AFTER_EU_VAT = int_env('FIRST_VM_ID_AFTER_EU_VAT') if DEBUG: From 73b590f48061b4057d93fa71f22646131aac9d3b Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 9 Dec 2019 14:42:05 +0530 Subject: [PATCH 016/471] Set EU VAT context for invoice_detail --- hosting/views.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index c5a4a761..157940fd 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1250,18 +1250,32 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): context['vm'] = vm_detail.__dict__ context['vm']['name'] = '{}-{}'.format( context['vm']['configuration'], context['vm']['vm_id']) - price, vat, vat_percent, discount = get_vm_price_with_vat( + user_vat_country = obj.order.billing_address.country + user_country_vat_rate = get_vat_rate_for_country( + user_vat_country) + price, vat, vat_percent, discount = get_vm_price_for_given_vat( cpu=context['vm']['cores'], ssd_size=context['vm']['disk_size'], memory=context['vm']['memory'], - pricing_name=(obj.order.vm_pricing.name - if obj.order.vm_pricing else 'default') + pricing_name=(obj.vm_pricing.name + if obj.vm_pricing else 'default'), + vat_rate=( + user_country_vat_rate * 100 + if obj.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT + else 7.7 + ) ) - context['vm']['vat'] = vat - context['vm']['price'] = price - context['vm']['discount'] = discount - context['vm']['vat_percent'] = vat_percent - context['vm']['total_price'] = price + vat - discount['amount'] + context['vm']["after_eu_vat_intro"] = ( + True if obj.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT + else False + ) + context['vm']["price"] = price + context['vm']["vat"] = vat + context['vm']["vat_percent"] = vat_percent + context['vm']["vat_country"] = user_vat_country + context['vm']["discount"] = discount + context['vm']["total_price"] = round( + price + vat - discount['amount'], 2) except VMDetail.DoesNotExist: # fallback to get it from the infrastructure try: From e334b01ad482033e72b00687a41731b9981f7797 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 9 Dec 2019 14:44:31 +0530 Subject: [PATCH 017/471] Fix the way we get variables --- hosting/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 157940fd..286da92a 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1257,16 +1257,16 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): cpu=context['vm']['cores'], ssd_size=context['vm']['disk_size'], memory=context['vm']['memory'], - pricing_name=(obj.vm_pricing.name - if obj.vm_pricing else 'default'), + pricing_name=(obj.order.vm_pricing.name + if obj.order.vm_pricing else 'default'), vat_rate=( user_country_vat_rate * 100 - if obj.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT + if obj.order.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT else 7.7 ) ) context['vm']["after_eu_vat_intro"] = ( - True if obj.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT + True if obj.order.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT else False ) context['vm']["price"] = price From 52717c2ce708d3bf95fea94939ab90ca98b0d367 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 9 Dec 2019 15:09:05 +0530 Subject: [PATCH 018/471] EU VAT for hosting flow --- hosting/views.py | 49 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 286da92a..c2421e56 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -882,20 +882,32 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): ) vm = manager.get_vm(obj.vm_id) context['vm'] = VirtualMachineSerializer(vm).data - price, vat, vat_percent, discount = get_vm_price_with_vat( + user_vat_country = obj.billing_address.country + user_country_vat_rate = get_vat_rate_for_country( + user_vat_country) + price, vat, vat_percent, discount = get_vm_price_for_given_vat( cpu=context['vm']['cores'], ssd_size=context['vm']['disk_size'], memory=context['vm']['memory'], pricing_name=(obj.vm_pricing.name - if obj.vm_pricing else 'default') + if obj.vm_pricing else 'default'), + vat_rate=( + user_country_vat_rate * 100 + if obj.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT + else 7.7 + ) ) - context['vm']['vat'] = vat - context['vm']['price'] = price - context['vm']['discount'] = discount - context['vm']['vat_percent'] = vat_percent - context['vm']['total_price'] = ( - price + vat - discount['amount'] + context['vm']["after_eu_vat_intro"] = ( + True if obj.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT + else False ) + context['vm']["price"] = price + context['vm']["vat"] = vat + context['vm']["vat_percent"] = vat_percent + context['vm']["vat_country"] = user_vat_country + context['vm']["discount"] = discount + context['vm']["total_price"] = round( + price + vat - discount['amount'], 2) except WrongIdError: messages.error( self.request, @@ -933,7 +945,26 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): context['cc_exp_year'] = card_detail.exp_year context['cc_exp_month'] = '{:02d}'.format(card_detail.exp_month) context['site_url'] = reverse('hosting:create_virtual_machine') - context['vm'] = self.request.session.get('specs') + vm_specs = self.request.session.get('specs') + user_vat_country = ( + self.request.session.get('billing_address_data').get("country") + ) + user_country_vat_rate = get_vat_rate_for_country(user_vat_country) + price, vat, vat_percent, discount = get_vm_price_for_given_vat( + cpu=vm_specs['cpu'], + memory=vm_specs['memory'], + ssd_size=vm_specs['disk_size'], + pricing_name=vm_specs['pricing_name'], + vat_rate=user_country_vat_rate * 100 + ) + vm_specs["price"] = price + vm_specs["vat"] = vat + vm_specs["vat_percent"] = vat_percent + vm_specs["vat_country"] = user_vat_country + vm_specs["discount"] = discount + vm_specs["total_price"] = round(price + vat - discount['amount'], + 2) + context['vm'] = vm_specs return context @method_decorator(decorators) From d0398ddec29c66095711428ffdfb6c9000bef55d Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 9 Dec 2019 15:15:21 +0530 Subject: [PATCH 019/471] Set after_eu_vat_intro for hosting VM buy flow --- hosting/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hosting/views.py b/hosting/views.py index c2421e56..df943cb5 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -964,6 +964,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): vm_specs["discount"] = discount vm_specs["total_price"] = round(price + vat - discount['amount'], 2) + context['vm']["after_eu_vat_intro"] = True context['vm'] = vm_specs return context From d2d9eafa415abd56d121eefafa51f8fd8b4569f4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 9 Dec 2019 15:19:59 +0530 Subject: [PATCH 020/471] Fix using wrongly copy/pasted variable --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index df943cb5..1228b569 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -964,7 +964,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): vm_specs["discount"] = discount vm_specs["total_price"] = round(price + vat - discount['amount'], 2) - context['vm']["after_eu_vat_intro"] = True + vm_specs["after_eu_vat_intro"] = True context['vm'] = vm_specs return context From 744e76c5df14b0a383ecfcec79af5536f02b6f8c Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 9 Dec 2019 17:47:44 +0530 Subject: [PATCH 021/471] Change price 15 CHF -> 10.5 CHF --- .../locale/de/LC_MESSAGES/django.po | 43 ++++++++++++------- .../datacenterlight/emails/welcome_user.html | 2 +- .../datacenterlight/emails/welcome_user.txt | 2 +- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index 2a76c118..ebb78a1c 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: 2019-11-15 17:33+0000\n" +"POT-Creation-Date: 2019-12-09 12:13+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,9 @@ 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 15CHF per month." -msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!" + +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 "ORDER VM" msgstr "VM BESTELLEN" @@ -214,16 +215,16 @@ msgid "" "Is creative, using a modern and alternative design for a data center in " "order to make it more sustainable and affordable at the same time." msgstr "" -"Es ist kreativ, da es sich ein modernes und alternatives Layout zu Nutze" -"macht um Nachhaltigkeit zu fördern und somit erschwingliche Preise bieten zu " -"können.
" +"Es ist kreativ, da es sich ein modernes und alternatives Layout zu " +"Nutzemacht um Nachhaltigkeit zu fördern und somit erschwingliche Preise " +"bieten zu können.
" msgid "" "Cuts down the costs for you by using FOSS (Free Open Source Software) " "exclusively, wherefore we can save money from paying licenses." msgstr "" -"Um unnötige Kosten zu sparen werden, wird ausschliesslich Software auf" -"Basis von FOSS (Free Open Source Software) eingesetzt und dadurch können auf " +"Um unnötige Kosten zu sparen werden, wird ausschliesslich Software aufBasis " +"von FOSS (Free Open Source Software) eingesetzt und dadurch können auf " "Lizenzgebühren verzichtet werden.
" msgid "Scale out" @@ -439,9 +440,6 @@ msgstr "Wiederholend" msgid "Subtotal" msgstr "Zwischensumme" -msgid "VAT" -msgstr "Mehrwertsteuer" - #, fuzzy, python-format #| msgid "" #| "By clicking \"Place order\" this plan will charge your credit card " @@ -453,7 +451,7 @@ msgstr "" "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s " "CHF pro Jahr belastet" - +#, python-format msgid "" "By clicking \"Place order\" this plan will charge your credit card account " "with %(total_price)s CHF/month" @@ -591,7 +589,8 @@ msgstr "Ungültige Speicher-Grösse" #, python-brace-format msgid "Incorrect pricing name. Please contact support{support_email}" -msgstr "Ungültige Preisbezeichnung. Bitte kontaktiere den 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" @@ -636,8 +635,15 @@ msgid "" "\n" "Cheers,\n" "Your Data Center Light team" -msgstr "Hallo {name},\n" "\n" "vielen Dank für deine Bestellung!\n" "Wir haben deine Bezahlung in Höhe von {amount:.2f} CHF erhalten. {recurring}\n" "\n" "Grüsse\n" -"Dein Data Center Light Team" +msgstr "" +"Hallo {name},\n" +"\n" +"vielen Dank für deine Bestellung!\n" +"Wir haben deine Bezahlung in Höhe von {amount:.2f} CHF erhalten. " +"{recurring}\n" +"\n" +"Grüsse\n" +"Dein Data Center Light Team" msgid "Thank you for the payment." msgstr "Danke für Deine Bestellung." @@ -645,7 +651,9 @@ 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 "Du wirst bald eine Bestätigungs-E-Mail über die Zahlung erhalten. Du kannst jederzeit unter info@ungleich.ch kontaktieren." +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." @@ -657,6 +665,9 @@ msgstr "" "Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " "auf sie zugreifen kannst." +#~ msgid "VAT" +#~ msgstr "Mehrwertsteuer" + #~ msgid "" #~ "You are not making any payment yet. After submitting your card " #~ "information, you will be taken to the Confirm Order Page." diff --git a/datacenterlight/templates/datacenterlight/emails/welcome_user.html b/datacenterlight/templates/datacenterlight/emails/welcome_user.html index f18f9750..25185618 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 15CHF per month.{% endblocktrans %} + {% blocktrans %}Try now, order a VM. VM price starts from only 10.5 CHF per month.{% endblocktrans %}

diff --git a/datacenterlight/templates/datacenterlight/emails/welcome_user.txt b/datacenterlight/templates/datacenterlight/emails/welcome_user.txt index 0e7820e6..772e51a5 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 15CHF per month.{% endblocktrans %} +{% blocktrans %}Try now, order a VM. VM price starts from only 10.5 CHF per month.{% endblocktrans %} {{ base_url }}{% url 'hosting:create_virtual_machine' %} From a6695a103ffa3a9bf138820d0da36eb84775f1fe Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 9 Dec 2019 18:05:57 +0530 Subject: [PATCH 022/471] Refactor PRE_EU_VAT_RATE + fix >= for first_vm_id_after_eu_vat --- dynamicweb/settings/base.py | 2 ++ hosting/views.py | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 70d3ec2c..fc971141 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -727,7 +727,9 @@ AUTH_SEED = env('AUTH_SEED') AUTH_REALM = env('AUTH_REALM') OTP_SERVER = env('OTP_SERVER') OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT') + FIRST_VM_ID_AFTER_EU_VAT = int_env('FIRST_VM_ID_AFTER_EU_VAT') +PRE_EU_VAT_RATE = float(env('PRE_EU_VAT_RATE')) if DEBUG: diff --git a/hosting/views.py b/hosting/views.py index 1228b569..e196d91f 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -859,8 +859,8 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): if obj.vm_pricing else 'default'), vat_rate= ( user_country_vat_rate * 100 - if obj.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT - else 7.7 + if obj.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT + else settings.PRE_EU_VAT_RATE ) ) context['vm']["after_eu_vat_intro"] = ( @@ -893,8 +893,8 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): if obj.vm_pricing else 'default'), vat_rate=( user_country_vat_rate * 100 - if obj.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT - else 7.7 + if obj.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT + else settings.PRE_EU_VAT_RATE ) ) context['vm']["after_eu_vat_intro"] = ( @@ -1293,8 +1293,8 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): if obj.order.vm_pricing else 'default'), vat_rate=( user_country_vat_rate * 100 - if obj.order.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT - else 7.7 + if obj.order.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT + else settings.PRE_EU_VAT_RATE ) ) context['vm']["after_eu_vat_intro"] = ( @@ -1322,7 +1322,12 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): ssd_size=context['vm']['disk_size'], memory=context['vm']['memory'], pricing_name=(obj.order.vm_pricing.name - if obj.order.vm_pricing else 'default') + if obj.order.vm_pricing else 'default'), + vat_rate=( + user_country_vat_rate * 100 + if obj.order.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT + else settings.PRE_EU_VAT_RATE + ) ) context['vm']['vat'] = vat context['vm']['price'] = price From fcc671a7072ebafc09aaac185b0a9a874f936325 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 9 Dec 2019 18:07:19 +0530 Subject: [PATCH 023/471] Fix >= for first_vm_id_after_eu_vat --- hosting/views.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index e196d91f..bd5d1889 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -864,7 +864,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): ) ) context['vm']["after_eu_vat_intro"] = ( - True if obj.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT + True if obj.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT else False ) context['vm']["price"] = price @@ -898,7 +898,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): ) ) context['vm']["after_eu_vat_intro"] = ( - True if obj.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT + True if obj.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT else False ) context['vm']["price"] = price @@ -1298,7 +1298,7 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): ) ) context['vm']["after_eu_vat_intro"] = ( - True if obj.order.vm_id > settings.FIRST_VM_ID_AFTER_EU_VAT + True if obj.order.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT else False ) context['vm']["price"] = price @@ -1329,12 +1329,9 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): else settings.PRE_EU_VAT_RATE ) ) - context['vm']['vat'] = vat - context['vm']['price'] = price - context['vm']['discount'] = discount - context['vm']['vat_percent'] = vat_percent - context['vm']['total_price'] = ( - price + vat - discount['amount'] + context['vm']["after_eu_vat_intro"] = ( + True if obj.order.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT + else False ) except TypeError: logger.error("Type error. Probably we " From cc027c24972414c959f8689e13a4353618cc2350 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 9 Dec 2019 18:07:46 +0530 Subject: [PATCH 024/471] Add eu vat code --- hosting/views.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index bd5d1889..21ede03e 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1317,7 +1317,10 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): ) vm = manager.get_vm(vm_id) context['vm'] = VirtualMachineSerializer(vm).data - price, vat, vat_percent, discount = get_vm_price_with_vat( + user_vat_country = obj.order.billing_address.country + user_country_vat_rate = get_vat_rate_for_country( + user_vat_country) + price, vat, vat_percent, discount = get_vm_price_for_given_vat( cpu=context['vm']['cores'], ssd_size=context['vm']['disk_size'], memory=context['vm']['memory'], @@ -1333,6 +1336,13 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): True if obj.order.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT else False ) + context['vm']["price"] = price + context['vm']["vat"] = vat + context['vm']["vat_percent"] = vat_percent + context['vm']["vat_country"] = user_vat_country + context['vm']["discount"] = discount + context['vm']["total_price"] = round( + price + vat - discount['amount'], 2) except TypeError: logger.error("Type error. Probably we " "came from a generic product. " From a09f95d619c0ffc574d44e7af2fc2a7dfc6cb601 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 9 Dec 2019 19:37:31 +0530 Subject: [PATCH 025/471] Update Changelog --- Changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Changelog b/Changelog index 43c8ebc3..17efb793 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,9 @@ +2.7: 2019-12-9 + * feature: EU VAT for new subscriptions (MR!721) + Notes for deployment: + - Add the following to .env file + - FIRST_VM_ID_AFTER_EU_VAT= + - PRE_EU_VAT_RATE=whatever the rate was before introduction of EU VAT (7.7 for example) 2.6.10: 2019-11-16 * translation: Add DE translations for features in 2.6.{8,9} by moep (MR!719) 2.6.9: 2019-11-15 From 3b9322b9297b48c1edf55416938000011880f31b Mon Sep 17 00:00:00 2001 From: meow Date: Tue, 10 Dec 2019 22:53:50 +0500 Subject: [PATCH 026/471] init commit --- .gitignore | 3 +- INSTALLATION.rst | 28 +- dynamicweb/settings/base.py | 38 ++- dynamicweb/settings/ldap_max_uid_file | 1 + hosting/views.py | 5 + .../migrations/0011_customuser_username.py | 20 ++ .../migrations/0012_auto_20191210_1141.py | 20 ++ .../migrations/0013_customuser_in_ldap.py | 20 ++ .../0014_remove_customuser_in_ldap.py | 19 ++ .../migrations/0015_customuser_in_ldap.py | 20 ++ membership/models.py | 69 ++++- requirements.archlinux.txt | 1 + utils/backend.py | 73 +++++ utils/ldap_manager.py | 279 ++++++++++++++++++ 14 files changed, 587 insertions(+), 9 deletions(-) create mode 100644 dynamicweb/settings/ldap_max_uid_file create mode 100644 membership/migrations/0011_customuser_username.py create mode 100644 membership/migrations/0012_auto_20191210_1141.py create mode 100644 membership/migrations/0013_customuser_in_ldap.py create mode 100644 membership/migrations/0014_remove_customuser_in_ldap.py create mode 100644 membership/migrations/0015_customuser_in_ldap.py create mode 100644 utils/backend.py create mode 100644 utils/ldap_manager.py diff --git a/.gitignore b/.gitignore index 1b2b4d16..2d923e99 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ __pycache__/ .ropeproject/ #django local_settings.py - +Pipfile media/ !media/keep /CACHE/ @@ -43,3 +43,4 @@ secret-key # to keep empty dirs !.gitkeep *.orig +.vscode/settings.json diff --git a/INSTALLATION.rst b/INSTALLATION.rst index ee36b3ad..efa299f3 100644 --- a/INSTALLATION.rst +++ b/INSTALLATION.rst @@ -10,13 +10,35 @@ Requirements Install ======= + +.. note:: + lxml that is one of the dependency of dynamicweb couldn't + get build on Python 3.7 so, please use Python 3.5. + + +First install packages from requirements.archlinux.txt or +requirements.debian.txt based on your distribution. + + The quick way: ``pip install -r requirements.txt`` Next find the dump.db file on stagging server. Path for the file is under the base application folder. +or you can create one for yourself by running the following commands on dynamicweb server + +.. code:: sh + + sudo su - postgres + pg_dump app > /tmp/postgres_db.bak + exit + cp /tmp/postgres_db.bak /root/postgres_db.bak + +Now, you can download this using sftp. + + Install the postgresql server and import the database:: - ``psql -d app < dump.db`` + ``psql -d app -U root < dump.db`` **No migration is needed after a clean install, and You are ready to start developing.** @@ -25,9 +47,9 @@ Development Project is separated in master branch and development branch, and feature branches. Master branch is currently used on `Digital Glarus `_ and `Ungleich blog `_. -If You are starting to create a new feature fork the github `repo `_ and branch the development branch. +If You are starting to create a new feature fork the github `repo `_ and branch the development branch. -After You have complited the task create a pull request and ask someone to review the code from other developers. +After You have completed the task, create a pull request and ask someone to review the code from other developers. **Cheat sheet for branching and forking**: diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index fc971141..dbebc36e 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -10,7 +10,10 @@ import os # dotenv import dotenv +import ldap + from django.utils.translation import ugettext_lazy as _ +from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion logger = logging.getLogger(__name__) @@ -52,7 +55,7 @@ PROJECT_DIR = os.path.abspath( ) # load .env file -dotenv.read_dotenv("{0}/.env".format(PROJECT_DIR)) +dotenv.load_dotenv("{0}/.env".format(PROJECT_DIR)) from multisite import SiteID @@ -240,12 +243,14 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'app', + 'USER': 'root' } } AUTHENTICATION_BACKENDS = ( + 'utils.backend.MyLDAPBackend', 'guardian.backends.ObjectPermissionBackend', - 'django.contrib.auth.backends.ModelBackend', + ) # Internationalization @@ -721,6 +726,35 @@ X_FRAME_OPTIONS = ('SAMEORIGIN' if X_FRAME_OPTIONS_ALLOW_FROM_URI is None else DEBUG = bool_env('DEBUG') + +# LDAP setup +LDAP_SERVER = env('LDAP_SERVER') +LDAP_ADMIN_DN = env('LDAP_ADMIN_DN') +LDAP_ADMIN_PASSWORD = env('LDAP_ADMIN_PASSWORD') +AUTH_LDAP_SERVER = env('LDAPSERVER') + +LDAP_CUSTOMER_DN = env('LDAP_CUSTOMER_DN') +LDAP_CUSTOMER_GROUP_ID = int(env('LDAP_CUSTOMER_GROUP_ID')) +LDAP_MAX_UID_FILE_PATH = os.environ.get('LDAP_MAX_UID_FILE_PATH', + os.path.join(os.path.abspath(os.path.dirname(__file__)), 'ldap_max_uid_file') +) +LDAP_DEFAULT_START_UID = int(env('LDAP_DEFAULT_START_UID')) + +# Search union over OUs +search_base = env('LDAPSEARCH').split() +search_base_ldap = [LDAPSearch(x, ldap.SCOPE_SUBTREE, "(uid=%(user)s)") for x in search_base] +AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*search_base_ldap) +AUTH_LDAP_START_TLS = bool(os.environ.get('LDAP_USE_TLS', False)) + +ENTIRE_SEARCH_BASE = env("ENTIRE_SEARCH_BASE") + + +AUTH_LDAP_USER_ATTR_MAP = { + "first_name": "givenName", + "last_name": "sn", + "email": "mail" +} + READ_VM_REALM = env('READ_VM_REALM') AUTH_NAME = env('AUTH_NAME') AUTH_SEED = env('AUTH_SEED') diff --git a/dynamicweb/settings/ldap_max_uid_file b/dynamicweb/settings/ldap_max_uid_file new file mode 100644 index 00000000..9c1cfb87 --- /dev/null +++ b/dynamicweb/settings/ldap_max_uid_file @@ -0,0 +1 @@ +10173 \ No newline at end of file diff --git a/hosting/views.py b/hosting/views.py index 21ede03e..7ee1b93b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -57,6 +57,8 @@ from utils.hosting_utils import ( from utils.mailer import BaseEmail from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task +from utils.ldap_manager import LdapManager + from utils.views import ( PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin, ResendActivationLinkViewMixin @@ -394,9 +396,12 @@ class PasswordResetConfirmView(HostingContextMixin, if user is not None and default_token_generator.check_token(user, token): if form.is_valid(): + ldap_manager = LdapManager() new_password = form.cleaned_data['new_password2'] + user.create_ldap_account() user.set_password(new_password) user.save() + ldap_manager.change_password(user.username, user.password) messages.success(request, _('Password has been reset.')) # Change opennebula password diff --git a/membership/migrations/0011_customuser_username.py b/membership/migrations/0011_customuser_username.py new file mode 100644 index 00000000..21a9cc14 --- /dev/null +++ b/membership/migrations/0011_customuser_username.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-10 10:52 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0010_customuser_import_stripe_bill_remark'), + ] + + operations = [ + migrations.AddField( + model_name='customuser', + name='username', + field=models.CharField(max_length=50, null=True), + ), + ] diff --git a/membership/migrations/0012_auto_20191210_1141.py b/membership/migrations/0012_auto_20191210_1141.py new file mode 100644 index 00000000..7a64373a --- /dev/null +++ b/membership/migrations/0012_auto_20191210_1141.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-10 11:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0011_customuser_username'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='username', + field=models.CharField(max_length=50, null=True, unique=True), + ), + ] diff --git a/membership/migrations/0013_customuser_in_ldap.py b/membership/migrations/0013_customuser_in_ldap.py new file mode 100644 index 00000000..81cd2fd7 --- /dev/null +++ b/membership/migrations/0013_customuser_in_ldap.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-10 15:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0012_auto_20191210_1141'), + ] + + operations = [ + migrations.AddField( + model_name='customuser', + name='in_ldap', + field=models.BooleanField(default=False), + ), + ] diff --git a/membership/migrations/0014_remove_customuser_in_ldap.py b/membership/migrations/0014_remove_customuser_in_ldap.py new file mode 100644 index 00000000..af594e1f --- /dev/null +++ b/membership/migrations/0014_remove_customuser_in_ldap.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-10 15:36 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0013_customuser_in_ldap'), + ] + + operations = [ + migrations.RemoveField( + model_name='customuser', + name='in_ldap', + ), + ] diff --git a/membership/migrations/0015_customuser_in_ldap.py b/membership/migrations/0015_customuser_in_ldap.py new file mode 100644 index 00000000..39c3384b --- /dev/null +++ b/membership/migrations/0015_customuser_in_ldap.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-10 17:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0014_remove_customuser_in_ldap'), + ] + + operations = [ + migrations.AddField( + model_name='customuser', + name='in_ldap', + field=models.BooleanField(default=False), + ), + ] diff --git a/membership/models.py b/membership/models.py index df5a5326..99180715 100644 --- a/membership/models.py +++ b/membership/models.py @@ -1,5 +1,6 @@ -from datetime import datetime +import logging +from datetime import datetime from django.conf import settings from django.contrib.auth.hashers import make_password from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, \ @@ -7,13 +8,16 @@ from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, \ from django.contrib.sites.models import Site from django.core.urlresolvers import reverse from django.core.validators import RegexValidator -from django.db import models +from django.db import models, IntegrityError from django.utils.crypto import get_random_string from django.utils.translation import ugettext_lazy as _ from utils.mailer import BaseEmail from utils.mailer import DigitalGlarusRegistrationMailer from utils.stripe_utils import StripeUtils +from utils.ldap_manager import LdapManager + +logger = logging.getLogger(__name__) REGISTRATION_MESSAGE = {'subject': "Validation mail", 'message': 'Please validate Your account under this link ' @@ -42,6 +46,7 @@ class MyUserManager(BaseUserManager): user.is_admin = False user.set_password(password) user.save(using=self._db) + user.create_ldap_account() return user def create_superuser(self, email, name, password): @@ -63,13 +68,43 @@ def get_validation_slug(): return make_password(None) +def get_first_and_last_name(full_name): + first_name, *last_name = full_name.split(" ") + first_name = first_name + last_name = " ".join(last_name) + return first_name, last_name + + +def assign_username(user): + if not user.username: + first_name, last_name = get_first_and_last_name(user.name) + user.username = first_name.lower() + last_name.lower() + user.username = "".join(user.username.split()) + try: + user.save() + except IntegrityError: + try: + user.username = user.username + str(user.id) + user.save() + except IntegrityError: + while True: + user.username = user.username + str(random.randint(0, 2 ** 50)) + try: + user.save() + except IntegrityError: + continue + else: + break + + class CustomUser(AbstractBaseUser, PermissionsMixin): VALIDATED_CHOICES = ((0, 'Not validated'), (1, 'Validated')) site = models.ForeignKey(Site, default=1) name = models.CharField(max_length=50) email = models.EmailField(unique=True) - + username = models.CharField(max_length=50, unique=True, null=True) validated = models.IntegerField(choices=VALIDATED_CHOICES, default=0) + in_ldap = models.BooleanField(default=False) # By default, we initialize the validation_slug with appropriate value # This is required for User(page) admin validation_slug = models.CharField( @@ -164,6 +199,34 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): # The user is identified by their email address return self.email + def create_ldap_account(self): + # create ldap account for user if it does not exists already. + if self.in_ldap: + return + + assign_username(self) + ldap_manager = LdapManager() + try: + user_exists_in_ldap, entries = ldap_manager.check_user_exists( + uid=self.username, + attributes=['uid', 'givenName', 'sn', 'mail', 'userPassword'], + search_base=settings.ENTIRE_SEARCH_BASE, + search_attr='uid' + ) + except Exception: + logger.exception("Exception occur while searching for user in LDAP") + else: + if not user_exists_in_ldap: + # IF no ldap account + first_name, last_name = get_first_and_last_name(self.name) + if not last_name: + last_name = first_name + + ldap_manager.create_user(self.username, password=self.password, + firstname=first_name, lastname=last_name, + email=self.email) + self.in_ldap = True + self.save() def __str__(self): # __unicode__ on Python 2 return self.email diff --git a/requirements.archlinux.txt b/requirements.archlinux.txt index b4cab6e4..15184f0d 100644 --- a/requirements.archlinux.txt +++ b/requirements.archlinux.txt @@ -1 +1,2 @@ +base-devel libmemcached diff --git a/utils/backend.py b/utils/backend.py new file mode 100644 index 00000000..f67763ca --- /dev/null +++ b/utils/backend.py @@ -0,0 +1,73 @@ + +import logging + +from membership.models import CustomUser +logger = logging.getLogger(__name__) + +class MyLDAPBackend(object): + def authenticate(self, email, password): + try: + user = CustomUser.objects.get(email=email) + except CustomUser.DoesNotExist: + # User does not exists in Database + return None + else: + user.create_ldap_account() + if user.check_password(password): + return user + else: + return None + + # # User exists in Database + # user.create_ldap_account() + # # User does not have a username + # if not user.username: + # assign_username(user) + # + # ldap_manager = LdapManager() + # try: + # user_exists_in_ldap, entries = ldap_manager.check_user_exists( + # uid=user.username, + # attributes=['uid', 'givenName', 'sn', 'mail', 'userPassword'], + # search_base=settings.ENTIRE_SEARCH_BASE, + # search_attr='uid' + # ) + # except Exception: + # logger.exception("Exception occur while searching for user in LDAP") + # else: + # ph = PasswordHasher() + # if user_exists_in_ldap: + # # User Exists in LDAP + # password_hash_from_ldap = entries[0]["userPassword"].value + # try: + # ph.verify(password_hash_from_ldap, password) + # except Exception: + # # Incorrect LDAP Password + # return None + # else: + # # Correct LDAP Password + # return user + # else: + # # User does not exists in LDAP + # if user.check_password(password): + # # Password is correct as per database + # first_name, last_name = get_first_and_last_name(user.name) + # if not last_name: + # last_name = first_name + # + # ldap_manager.create_user(user.username, password=ph.hash(password), + # firstname=first_name, lastname=last_name, + # email=user.email) + # user.password = "IN_LDAP" + # user.save() + # return user + # else: + # # Incorrect Password + # print("Incorrect password") + # return None + + def get_user(self, user_id): + try: + return CustomUser.objects.get(pk=user_id) + except CustomUser.DoesNotExist: + return None diff --git a/utils/ldap_manager.py b/utils/ldap_manager.py new file mode 100644 index 00000000..602bf6f2 --- /dev/null +++ b/utils/ldap_manager.py @@ -0,0 +1,279 @@ +import base64 +import hashlib +import random +import ldap3 +import logging + +from django.conf import settings + +logger = logging.getLogger(__name__) + + +class LdapManager: + __instance = None + + def __new__(cls): + if LdapManager.__instance is None: + LdapManager.__instance = object.__new__(cls) + return LdapManager.__instance + + def __init__(self): + """ + Initialize the LDAP subsystem. + """ + self.rng = random.SystemRandom() + self.server = ldap3.Server(settings.AUTH_LDAP_SERVER) + + def get_admin_conn(self): + """ + Return a bound :class:`ldap3.Connection` instance which has write + permissions on the dn in which the user accounts reside. + """ + conn = self.get_conn(user=settings.LDAP_ADMIN_DN, + password=settings.LDAP_ADMIN_PASSWORD, + raise_exceptions=True) + conn.bind() + return conn + + def get_conn(self, **kwargs): + """ + Return an unbound :class:`ldap3.Connection` which talks to the configured + LDAP server. + + The *kwargs* are passed to the constructor of :class:`ldap3.Connection` and + can be used to set *user*, *password* and other useful arguments. + """ + return ldap3.Connection(self.server, **kwargs) + + def _ssha_password(self, password): + """ + Apply the SSHA password hashing scheme to the given *password*. + *password* must be a :class:`bytes` object, containing the utf-8 + encoded password. + + Return a :class:`bytes` object containing ``ascii``-compatible data + which can be used as LDAP value, e.g. after armoring it once more using + base64 or decoding it to unicode from ``ascii``. + """ + SALT_BYTES = 15 + + sha1 = hashlib.sha1() + salt = self.rng.getrandbits(SALT_BYTES * 8).to_bytes(SALT_BYTES, + "little") + sha1.update(password) + sha1.update(salt) + + digest = sha1.digest() + passwd = b"{SSHA}" + base64.b64encode(digest + salt) + return passwd + + def create_user(self, user, password, firstname, lastname, email): + conn = self.get_admin_conn() + uidNumber = self._get_max_uid() + 1 + logger.debug("uidNumber={uidNumber}".format(uidNumber=uidNumber)) + user_exists = True + while user_exists: + user_exists, _ = self.check_user_exists( + "", + '(&(objectClass=inetOrgPerson)(objectClass=posixAccount)' + '(objectClass=top)(uidNumber={uidNumber}))'.format( + uidNumber=uidNumber + ) + ) + if user_exists: + logger.debug( + "{uid} exists. Trying next.".format(uid=uidNumber) + ) + uidNumber += 1 + logger.debug("{uid} does not exist. Using it".format(uid=uidNumber)) + self._set_max_uid(uidNumber) + try: + uid = user + conn.add("uid={uid},{customer_dn}".format( + uid=uid, customer_dn=settings.LDAP_CUSTOMER_DN + ), + ["inetOrgPerson", "posixAccount", "ldapPublickey"], + { + "uid": [uid], + "sn": [lastname], + "givenName": [firstname], + "cn": [uid], + "displayName": ["{} {}".format(firstname, lastname)], + "uidNumber": [str(uidNumber)], + "gidNumber": [str(settings.LDAP_CUSTOMER_GROUP_ID)], + "loginShell": ["/bin/bash"], + "homeDirectory": ["/home/{}".format(user)], + "mail": email, + "userPassword": [password] + } + ) + logger.debug('Created user %s %s' % (user.encode('utf-8'), + uidNumber)) + except Exception as ex: + logger.debug('Could not create user %s' % user.encode('utf-8')) + logger.error("Exception: " + str(ex)) + raise Exception(ex) + finally: + conn.unbind() + + def change_password(self, uid, new_password): + """ + Changes the password of the user identified by user_dn + + :param uid: str The uid that identifies the user + :param new_password: str The new password string + :return: True if password was changed successfully False otherwise + """ + conn = self.get_admin_conn() + + # Make sure the user exists first to change his/her details + user_exists, entries = self.check_user_exists( + uid=uid, + search_base=settings.ENTIRE_SEARCH_BASE + ) + return_val = False + if user_exists: + try: + return_val = conn.modify( + entries[0].entry_dn, + { + "userpassword": ( + ldap3.MODIFY_REPLACE, + [new_password] + ) + } + ) + except Exception as ex: + logger.error("Exception: " + str(ex)) + else: + logger.error("User {} not found".format(uid)) + + conn.unbind() + return return_val + + def change_user_details(self, uid, details): + """ + Updates the user details as per given values in kwargs of the user + identified by user_dn. + + Assumes that all attributes passed in kwargs are valid. + + :param uid: str The uid that identifies the user + :param details: dict A dictionary containing the new values + :return: True if user details were updated successfully False otherwise + """ + conn = self.get_admin_conn() + + # Make sure the user exists first to change his/her details + user_exists, entries = self.check_user_exists( + uid=uid, + search_base=settings.ENTIRE_SEARCH_BASE + ) + + return_val = False + if user_exists: + details_dict = {k: (ldap3.MODIFY_REPLACE, [v.encode("utf-8")]) for + k, v in details.items()} + try: + return_val = conn.modify(entries[0].entry_dn, details_dict) + msg = "success" + except Exception as ex: + msg = str(ex) + logger.error("Exception: " + msg) + finally: + conn.unbind() + else: + msg = "User {} not found".format(uid) + logger.error(msg) + conn.unbind() + return return_val, msg + + def check_user_exists(self, uid, search_filter="", attributes=None, + search_base=settings.LDAP_CUSTOMER_DN, search_attr="uid"): + """ + Check if the user with the given uid exists in the customer group. + + :param uid: str representing the user + :param search_filter: str representing the filter condition to find + users. If its empty, the search finds the user with + the given uid. + :param attributes: list A list of str representing all the attributes + to be obtained in the result entries + :param search_base: str + :return: tuple (bool, [ldap3.abstract.entry.Entry ..]) + A bool indicating if the user exists + A list of all entries obtained in the search + """ + conn = self.get_admin_conn() + entries = [] + try: + result = conn.search( + search_base=search_base, + search_filter=search_filter if len(search_filter) > 0 else + '(uid={uid})'.format(uid=uid), + attributes=attributes + ) + entries = conn.entries + finally: + conn.unbind() + return result, entries + + def delete_user(self, uid): + """ + Deletes the user with the given uid from ldap + + :param uid: str representing the user + :return: True if the delete was successful False otherwise + """ + conn = self.get_admin_conn() + try: + return_val = conn.delete( + ("uid={uid}," + settings.LDAP_CUSTOMER_DN).format(uid=uid), + ) + msg = "success" + except Exception as ex: + msg = str(ex) + logger.error("Exception: " + msg) + return_val = False + finally: + conn.unbind() + return return_val, msg + + def _set_max_uid(self, max_uid): + """ + a utility function to save max_uid value to a file + + :param max_uid: an integer representing the max uid + :return: + """ + with open(settings.LDAP_MAX_UID_FILE_PATH, 'w+') as handler: + handler.write(str(max_uid)) + + def _get_max_uid(self): + """ + A utility function to read the max uid value that was previously set + + :return: An integer representing the max uid value that was previously + set + """ + try: + with open(settings.LDAP_MAX_UID_FILE_PATH, 'r+') as handler: + try: + return_value = int(handler.read()) + except ValueError as ve: + logger.error( + "Error reading int value from {}. {}" + "Returning default value {} instead".format( + settings.LDAP_MAX_UID_PATH, + str(ve), + settings.LDAP_DEFAULT_START_UID + ) + ) + return_value = settings.LDAP_DEFAULT_START_UID + return return_value + except FileNotFoundError as fnfe: + logger.error("File not found : " + str(fnfe)) + return_value = settings.LDAP_DEFAULT_START_UID + logger.error("So, returning UID={}".format(return_value)) + return return_value + From db1da3af4c4087714e99693d49dfff822ddc6ff9 Mon Sep 17 00:00:00 2001 From: meow Date: Tue, 10 Dec 2019 23:01:07 +0500 Subject: [PATCH 027/471] use python-dotenv instead of django-dotenv --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c60c83e9..b77e4f51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ django-classy-tags==0.7.2 django-cms==3.2.5 django-compressor==2.0 django-debug-toolbar==1.4 -django-dotenv==1.4.1 +python-dotenv==0.10.3 django-extensions==1.6.7 django-filer==1.2.0 django-filter==0.13.0 From fbfc1152b8592f301a0b4f7912c0ef32cd4254ca Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 12 Dec 2019 17:42:18 +0530 Subject: [PATCH 028/471] Remove unknown or unspecified country option --- utils/fields.py | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/fields.py b/utils/fields.py index c7f1a54b..48a606cc 100644 --- a/utils/fields.py +++ b/utils/fields.py @@ -241,7 +241,6 @@ COUNTRIES = ( ('ZM', _('Zambia')), ('ZR', _('Zaire')), ('ZW', _('Zimbabwe')), - ('ZZ', _('Unknown or unspecified country')), ) From 9970bd992534728a0dbc5fde825eea595c129c0d Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 12 Dec 2019 21:33:29 +0530 Subject: [PATCH 029/471] Remove user for db --- dynamicweb/settings/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index dbebc36e..bc71d6bf 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -243,7 +243,6 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'app', - 'USER': 'root' } } From 37a3d21e0cab91c43e6ffacb99ec9338781ee949 Mon Sep 17 00:00:00 2001 From: meow Date: Thu, 12 Dec 2019 22:19:10 +0500 Subject: [PATCH 030/471] cleanup, ldap3 added to requirements.txt --- dynamicweb/settings/base.py | 5 ----- requirements.txt | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index bc71d6bf..fcd921a8 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -10,10 +10,7 @@ import os # dotenv import dotenv -import ldap - from django.utils.translation import ugettext_lazy as _ -from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion logger = logging.getLogger(__name__) @@ -741,8 +738,6 @@ LDAP_DEFAULT_START_UID = int(env('LDAP_DEFAULT_START_UID')) # Search union over OUs search_base = env('LDAPSEARCH').split() -search_base_ldap = [LDAPSearch(x, ldap.SCOPE_SUBTREE, "(uid=%(user)s)") for x in search_base] -AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*search_base_ldap) AUTH_LDAP_START_TLS = bool(os.environ.get('LDAP_USE_TLS', False)) ENTIRE_SEARCH_BASE = env("ENTIRE_SEARCH_BASE") diff --git a/requirements.txt b/requirements.txt index b77e4f51..5fb2ec67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -63,6 +63,7 @@ djangocms-text-ckeditor==2.9.3 djangocms-video==1.0.0 easy-thumbnails==2.3 html5lib==0.9999999 +ldap3==2.6.1 lxml==3.6.0 model-mommy==1.2.6 phonenumbers==7.4.0 From c96aff16af60080ac6dc06badedfb8fc963f14d0 Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 13 Dec 2019 15:05:27 +0500 Subject: [PATCH 031/471] username check added for ldap --- dynamicweb/settings/ldap_max_uid_file | 2 +- membership/models.py | 32 +++++++++++++++------------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/dynamicweb/settings/ldap_max_uid_file b/dynamicweb/settings/ldap_max_uid_file index 9c1cfb87..4c2a2049 100644 --- a/dynamicweb/settings/ldap_max_uid_file +++ b/dynamicweb/settings/ldap_max_uid_file @@ -1 +1 @@ -10173 \ No newline at end of file +10178 \ No newline at end of file diff --git a/membership/models.py b/membership/models.py index 99180715..ea761d99 100644 --- a/membership/models.py +++ b/membership/models.py @@ -1,4 +1,5 @@ import logging +import random from datetime import datetime from django.conf import settings @@ -77,24 +78,27 @@ def get_first_and_last_name(full_name): def assign_username(user): if not user.username: + ldap_manager = LdapManager() + + # Try to come up with a username first_name, last_name = get_first_and_last_name(user.name) user.username = first_name.lower() + last_name.lower() user.username = "".join(user.username.split()) - try: - user.save() - except IntegrityError: - try: - user.username = user.username + str(user.id) - user.save() - except IntegrityError: - while True: + + exist = True + while exist: + # Check if it exists + exist, entries = ldap_manager.check_user_exists(user.username) + if exist: + # If username exists in ldap, come up with a new user name and check it again + user.username = user.username + str(random.randint(0, 2 ** 50)) + else: + # If username does not exists in ldap, try to save it in database + try: + user.save() + except IntegrityError: + # If username exists in database then come up with a new username user.username = user.username + str(random.randint(0, 2 ** 50)) - try: - user.save() - except IntegrityError: - continue - else: - break class CustomUser(AbstractBaseUser, PermissionsMixin): From b4995336c6fec156a7889f2d66efe0eecc24f03a Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 13 Dec 2019 17:52:00 +0500 Subject: [PATCH 032/471] username would consist of only alphanumerics, ldap fields are encoded in utf-8 --- dynamicweb/settings/ldap_max_uid_file | 2 +- membership/models.py | 9 +++++---- utils/ldap_manager.py | 17 +++++++++-------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/dynamicweb/settings/ldap_max_uid_file b/dynamicweb/settings/ldap_max_uid_file index 4c2a2049..78f6c9e8 100644 --- a/dynamicweb/settings/ldap_max_uid_file +++ b/dynamicweb/settings/ldap_max_uid_file @@ -1 +1 @@ -10178 \ No newline at end of file +10185 \ No newline at end of file diff --git a/membership/models.py b/membership/models.py index ea761d99..3d15fd42 100644 --- a/membership/models.py +++ b/membership/models.py @@ -82,8 +82,9 @@ def assign_username(user): # Try to come up with a username first_name, last_name = get_first_and_last_name(user.name) - user.username = first_name.lower() + last_name.lower() - user.username = "".join(user.username.split()) + user.username = first_name + last_name + user.username = "".join(user.username.split()).lower() + user.username = "".join([char for char in user.username if char.isalnum()]) exist = True while exist: @@ -91,14 +92,14 @@ def assign_username(user): exist, entries = ldap_manager.check_user_exists(user.username) if exist: # If username exists in ldap, come up with a new user name and check it again - user.username = user.username + str(random.randint(0, 2 ** 50)) + user.username = user.username + str(random.randint(0, 2 ** 10)) else: # If username does not exists in ldap, try to save it in database try: user.save() except IntegrityError: # If username exists in database then come up with a new username - user.username = user.username + str(random.randint(0, 2 ** 50)) + user.username = user.username + str(random.randint(0, 2 ** 10)) class CustomUser(AbstractBaseUser, PermissionsMixin): diff --git a/utils/ldap_manager.py b/utils/ldap_manager.py index 602bf6f2..ee16937d 100644 --- a/utils/ldap_manager.py +++ b/utils/ldap_manager.py @@ -88,23 +88,23 @@ class LdapManager: logger.debug("{uid} does not exist. Using it".format(uid=uidNumber)) self._set_max_uid(uidNumber) try: - uid = user + uid = user.encode("utf-8") conn.add("uid={uid},{customer_dn}".format( uid=uid, customer_dn=settings.LDAP_CUSTOMER_DN ), ["inetOrgPerson", "posixAccount", "ldapPublickey"], { "uid": [uid], - "sn": [lastname], - "givenName": [firstname], + "sn": [lastname.encode("utf-8")], + "givenName": [firstname.encode("utf-8")], "cn": [uid], - "displayName": ["{} {}".format(firstname, lastname)], + "displayName": ["{} {}".format(firstname, lastname).encode("utf-8")], "uidNumber": [str(uidNumber)], "gidNumber": [str(settings.LDAP_CUSTOMER_GROUP_ID)], "loginShell": ["/bin/bash"], - "homeDirectory": ["/home/{}".format(user)], - "mail": email, - "userPassword": [password] + "homeDirectory": ["/home/{}".format(user).encode("utf-8")], + "mail": email.encode("utf-8"), + "userPassword": [password.encode("utf-8")] } ) logger.debug('Created user %s %s' % (user.encode('utf-8'), @@ -139,7 +139,7 @@ class LdapManager: { "userpassword": ( ldap3.MODIFY_REPLACE, - [new_password] + [new_password.encode("utf-8")] ) } ) @@ -151,6 +151,7 @@ class LdapManager: conn.unbind() return return_val + def change_user_details(self, uid, details): """ Updates the user details as per given values in kwargs of the user From 2a1932e052bfaea99201f82e1342fbd36e9b0442 Mon Sep 17 00:00:00 2001 From: meow Date: Fri, 13 Dec 2019 20:37:30 +0500 Subject: [PATCH 033/471] Added validator to allow only letters + spaces + hyphen, Normalizing usernames to ASCII --- dynamicweb/settings/ldap_max_uid_file | 2 +- .../migrations/0016_auto_20191213_1309.py | 20 ++++++++ membership/models.py | 23 ++++++--- utils/backend.py | 49 +------------------ utils/migrations/0007_auto_20191213_1309.py | 26 ++++++++++ 5 files changed, 65 insertions(+), 55 deletions(-) create mode 100644 membership/migrations/0016_auto_20191213_1309.py create mode 100644 utils/migrations/0007_auto_20191213_1309.py diff --git a/dynamicweb/settings/ldap_max_uid_file b/dynamicweb/settings/ldap_max_uid_file index 78f6c9e8..d3cdc227 100644 --- a/dynamicweb/settings/ldap_max_uid_file +++ b/dynamicweb/settings/ldap_max_uid_file @@ -1 +1 @@ -10185 \ No newline at end of file +10192 \ No newline at end of file diff --git a/membership/migrations/0016_auto_20191213_1309.py b/membership/migrations/0016_auto_20191213_1309.py new file mode 100644 index 00000000..fe888c03 --- /dev/null +++ b/membership/migrations/0016_auto_20191213_1309.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-13 13:09 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0015_customuser_in_ldap'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='username', + field=models.CharField(max_length=60, null=True, unique=True), + ), + ] diff --git a/membership/models.py b/membership/models.py index 3d15fd42..dd7b1363 100644 --- a/membership/models.py +++ b/membership/models.py @@ -1,5 +1,6 @@ import logging import random +import unicodedata from datetime import datetime from django.conf import settings @@ -12,6 +13,8 @@ from django.core.validators import RegexValidator from django.db import models, IntegrityError from django.utils.crypto import get_random_string from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ from utils.mailer import BaseEmail from utils.mailer import DigitalGlarusRegistrationMailer @@ -82,10 +85,8 @@ def assign_username(user): # Try to come up with a username first_name, last_name = get_first_and_last_name(user.name) - user.username = first_name + last_name - user.username = "".join(user.username.split()).lower() - user.username = "".join([char for char in user.username if char.isalnum()]) - + user.username = unicodedata.normalize('NFKD', first_name + last_name) + user.username = "".join([char for char in user.username if char.isalnum()]).lower() exist = True while exist: # Check if it exists @@ -102,12 +103,21 @@ def assign_username(user): user.username = user.username + str(random.randint(0, 2 ** 10)) +def validate_name(value): + valid_chars = [char for char in value if (char.isalpha() or char == "-" or char == " ")] + if len(valid_chars) < len(value): + raise ValidationError( + _('%(value)s is not a valid name. A valid name can only include letters, spaces or -'), + params={'value': value}, + ) + + class CustomUser(AbstractBaseUser, PermissionsMixin): VALIDATED_CHOICES = ((0, 'Not validated'), (1, 'Validated')) site = models.ForeignKey(Site, default=1) - name = models.CharField(max_length=50) + name = models.CharField(max_length=50, validators=[validate_name]) email = models.EmailField(unique=True) - username = models.CharField(max_length=50, unique=True, null=True) + username = models.CharField(max_length=60, unique=True, null=True) validated = models.IntegerField(choices=VALIDATED_CHOICES, default=0) in_ldap = models.BooleanField(default=False) # By default, we initialize the validation_slug with appropriate value @@ -232,6 +242,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): email=self.email) self.in_ldap = True self.save() + def __str__(self): # __unicode__ on Python 2 return self.email diff --git a/utils/backend.py b/utils/backend.py index f67763ca..485dfe93 100644 --- a/utils/backend.py +++ b/utils/backend.py @@ -4,6 +4,7 @@ import logging from membership.models import CustomUser logger = logging.getLogger(__name__) + class MyLDAPBackend(object): def authenticate(self, email, password): try: @@ -18,54 +19,6 @@ class MyLDAPBackend(object): else: return None - # # User exists in Database - # user.create_ldap_account() - # # User does not have a username - # if not user.username: - # assign_username(user) - # - # ldap_manager = LdapManager() - # try: - # user_exists_in_ldap, entries = ldap_manager.check_user_exists( - # uid=user.username, - # attributes=['uid', 'givenName', 'sn', 'mail', 'userPassword'], - # search_base=settings.ENTIRE_SEARCH_BASE, - # search_attr='uid' - # ) - # except Exception: - # logger.exception("Exception occur while searching for user in LDAP") - # else: - # ph = PasswordHasher() - # if user_exists_in_ldap: - # # User Exists in LDAP - # password_hash_from_ldap = entries[0]["userPassword"].value - # try: - # ph.verify(password_hash_from_ldap, password) - # except Exception: - # # Incorrect LDAP Password - # return None - # else: - # # Correct LDAP Password - # return user - # else: - # # User does not exists in LDAP - # if user.check_password(password): - # # Password is correct as per database - # first_name, last_name = get_first_and_last_name(user.name) - # if not last_name: - # last_name = first_name - # - # ldap_manager.create_user(user.username, password=ph.hash(password), - # firstname=first_name, lastname=last_name, - # email=user.email) - # user.password = "IN_LDAP" - # user.save() - # return user - # else: - # # Incorrect Password - # print("Incorrect password") - # return None - def get_user(self, user_id): try: return CustomUser.objects.get(pk=user_id) diff --git a/utils/migrations/0007_auto_20191213_1309.py b/utils/migrations/0007_auto_20191213_1309.py new file mode 100644 index 00000000..a292672d --- /dev/null +++ b/utils/migrations/0007_auto_20191213_1309.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-13 13:09 +from __future__ import unicode_literals + +from django.db import migrations +import utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('utils', '0006_auto_20170810_1742'), + ] + + operations = [ + migrations.AlterField( + model_name='billingaddress', + name='country', + field=utils.fields.CountryField(choices=[('AD', 'Andorra'), ('AE', 'United Arab Emirates'), ('AF', 'Afghanistan'), ('AG', 'Antigua & Barbuda'), ('AI', 'Anguilla'), ('AL', 'Albania'), ('AM', 'Armenia'), ('AN', 'Netherlands Antilles'), ('AO', 'Angola'), ('AQ', 'Antarctica'), ('AR', 'Argentina'), ('AS', 'American Samoa'), ('AT', 'Austria'), ('AU', 'Australia'), ('AW', 'Aruba'), ('AZ', 'Azerbaijan'), ('BA', 'Bosnia and Herzegovina'), ('BB', 'Barbados'), ('BD', 'Bangladesh'), ('BE', 'Belgium'), ('BF', 'Burkina Faso'), ('BG', 'Bulgaria'), ('BH', 'Bahrain'), ('BI', 'Burundi'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BN', 'Brunei Darussalam'), ('BO', 'Bolivia'), ('BR', 'Brazil'), ('BS', 'Bahama'), ('BT', 'Bhutan'), ('BV', 'Bouvet Island'), ('BW', 'Botswana'), ('BY', 'Belarus'), ('BZ', 'Belize'), ('CA', 'Canada'), ('CC', 'Cocos (Keeling) Islands'), ('CF', 'Central African Republic'), ('CG', 'Congo'), ('CH', 'Switzerland'), ('CI', 'Ivory Coast'), ('CK', 'Cook Iislands'), ('CL', 'Chile'), ('CM', 'Cameroon'), ('CN', 'China'), ('CO', 'Colombia'), ('CR', 'Costa Rica'), ('CU', 'Cuba'), ('CV', 'Cape Verde'), ('CX', 'Christmas Island'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DE', 'Germany'), ('DJ', 'Djibouti'), ('DK', 'Denmark'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('DZ', 'Algeria'), ('EC', 'Ecuador'), ('EE', 'Estonia'), ('EG', 'Egypt'), ('EH', 'Western Sahara'), ('ER', 'Eritrea'), ('ES', 'Spain'), ('ET', 'Ethiopia'), ('FI', 'Finland'), ('FJ', 'Fiji'), ('FK', 'Falkland Islands (Malvinas)'), ('FM', 'Micronesia'), ('FO', 'Faroe Islands'), ('FR', 'France'), ('FX', 'France, Metropolitan'), ('GA', 'Gabon'), ('GB', 'United Kingdom (Great Britain)'), ('GD', 'Grenada'), ('GE', 'Georgia'), ('GF', 'French Guiana'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GL', 'Greenland'), ('GM', 'Gambia'), ('GN', 'Guinea'), ('GP', 'Guadeloupe'), ('GQ', 'Equatorial Guinea'), ('GR', 'Greece'), ('GS', 'South Georgia and the South Sandwich Islands'), ('GT', 'Guatemala'), ('GU', 'Guam'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HK', 'Hong Kong'), ('HM', 'Heard & McDonald Islands'), ('HN', 'Honduras'), ('HR', 'Croatia'), ('HT', 'Haiti'), ('HU', 'Hungary'), ('ID', 'Indonesia'), ('IE', 'Ireland'), ('IL', 'Israel'), ('IN', 'India'), ('IO', 'British Indian Ocean Territory'), ('IQ', 'Iraq'), ('IR', 'Islamic Republic of Iran'), ('IS', 'Iceland'), ('IT', 'Italy'), ('JM', 'Jamaica'), ('JO', 'Jordan'), ('JP', 'Japan'), ('KE', 'Kenya'), ('KG', 'Kyrgyzstan'), ('KH', 'Cambodia'), ('KI', 'Kiribati'), ('KM', 'Comoros'), ('KN', 'St. Kitts and Nevis'), ('KP', "Korea, Democratic People's Republic of"), ('KR', 'Korea, Republic of'), ('KW', 'Kuwait'), ('KY', 'Cayman Islands'), ('KZ', 'Kazakhstan'), ('LA', "Lao People's Democratic Republic"), ('LB', 'Lebanon'), ('LC', 'Saint Lucia'), ('LI', 'Liechtenstein'), ('LK', 'Sri Lanka'), ('LR', 'Liberia'), ('LS', 'Lesotho'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('LV', 'Latvia'), ('LY', 'Libyan Arab Jamahiriya'), ('MA', 'Morocco'), ('MC', 'Monaco'), ('MD', 'Moldova, Republic of'), ('MG', 'Madagascar'), ('MH', 'Marshall Islands'), ('ML', 'Mali'), ('MN', 'Mongolia'), ('MM', 'Myanmar'), ('MO', 'Macau'), ('MP', 'Northern Mariana Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MS', 'Monserrat'), ('MT', 'Malta'), ('MU', 'Mauritius'), ('MV', 'Maldives'), ('MW', 'Malawi'), ('MX', 'Mexico'), ('MY', 'Malaysia'), ('MZ', 'Mozambique'), ('NA', 'Namibia'), ('NC', 'New Caledonia'), ('NE', 'Niger'), ('NF', 'Norfolk Island'), ('NG', 'Nigeria'), ('NI', 'Nicaragua'), ('NL', 'Netherlands'), ('NO', 'Norway'), ('NP', 'Nepal'), ('NR', 'Nauru'), ('NU', 'Niue'), ('NZ', 'New Zealand'), ('OM', 'Oman'), ('PA', 'Panama'), ('PE', 'Peru'), ('PF', 'French Polynesia'), ('PG', 'Papua New Guinea'), ('PH', 'Philippines'), ('PK', 'Pakistan'), ('PL', 'Poland'), ('PM', 'St. Pierre & Miquelon'), ('PN', 'Pitcairn'), ('PR', 'Puerto Rico'), ('PT', 'Portugal'), ('PW', 'Palau'), ('PY', 'Paraguay'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russian Federation'), ('RW', 'Rwanda'), ('SA', 'Saudi Arabia'), ('SB', 'Solomon Islands'), ('SC', 'Seychelles'), ('SD', 'Sudan'), ('SE', 'Sweden'), ('SG', 'Singapore'), ('SH', 'St. Helena'), ('SI', 'Slovenia'), ('SJ', 'Svalbard & Jan Mayen Islands'), ('SK', 'Slovakia'), ('SL', 'Sierra Leone'), ('SM', 'San Marino'), ('SN', 'Senegal'), ('SO', 'Somalia'), ('SR', 'Suriname'), ('ST', 'Sao Tome & Principe'), ('SV', 'El Salvador'), ('SY', 'Syrian Arab Republic'), ('SZ', 'Swaziland'), ('TC', 'Turks & Caicos Islands'), ('TD', 'Chad'), ('TF', 'French Southern Territories'), ('TG', 'Togo'), ('TH', 'Thailand'), ('TJ', 'Tajikistan'), ('TK', 'Tokelau'), ('TM', 'Turkmenistan'), ('TN', 'Tunisia'), ('TO', 'Tonga'), ('TP', 'East Timor'), ('TR', 'Turkey'), ('TT', 'Trinidad & Tobago'), ('TV', 'Tuvalu'), ('TW', 'Taiwan, Province of China'), ('TZ', 'Tanzania, United Republic of'), ('UA', 'Ukraine'), ('UG', 'Uganda'), ('UM', 'United States Minor Outlying Islands'), ('US', 'United States of America'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VA', 'Vatican City State (Holy See)'), ('VC', 'St. Vincent & the Grenadines'), ('VE', 'Venezuela'), ('VG', 'British Virgin Islands'), ('VI', 'United States Virgin Islands'), ('VN', 'Viet Nam'), ('VU', 'Vanuatu'), ('WF', 'Wallis & Futuna Islands'), ('WS', 'Samoa'), ('YE', 'Yemen'), ('YT', 'Mayotte'), ('YU', 'Yugoslavia'), ('ZA', 'South Africa'), ('ZM', 'Zambia'), ('ZR', 'Zaire'), ('ZW', 'Zimbabwe')], default='CH', max_length=2), + ), + migrations.AlterField( + model_name='userbillingaddress', + name='country', + field=utils.fields.CountryField(choices=[('AD', 'Andorra'), ('AE', 'United Arab Emirates'), ('AF', 'Afghanistan'), ('AG', 'Antigua & Barbuda'), ('AI', 'Anguilla'), ('AL', 'Albania'), ('AM', 'Armenia'), ('AN', 'Netherlands Antilles'), ('AO', 'Angola'), ('AQ', 'Antarctica'), ('AR', 'Argentina'), ('AS', 'American Samoa'), ('AT', 'Austria'), ('AU', 'Australia'), ('AW', 'Aruba'), ('AZ', 'Azerbaijan'), ('BA', 'Bosnia and Herzegovina'), ('BB', 'Barbados'), ('BD', 'Bangladesh'), ('BE', 'Belgium'), ('BF', 'Burkina Faso'), ('BG', 'Bulgaria'), ('BH', 'Bahrain'), ('BI', 'Burundi'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BN', 'Brunei Darussalam'), ('BO', 'Bolivia'), ('BR', 'Brazil'), ('BS', 'Bahama'), ('BT', 'Bhutan'), ('BV', 'Bouvet Island'), ('BW', 'Botswana'), ('BY', 'Belarus'), ('BZ', 'Belize'), ('CA', 'Canada'), ('CC', 'Cocos (Keeling) Islands'), ('CF', 'Central African Republic'), ('CG', 'Congo'), ('CH', 'Switzerland'), ('CI', 'Ivory Coast'), ('CK', 'Cook Iislands'), ('CL', 'Chile'), ('CM', 'Cameroon'), ('CN', 'China'), ('CO', 'Colombia'), ('CR', 'Costa Rica'), ('CU', 'Cuba'), ('CV', 'Cape Verde'), ('CX', 'Christmas Island'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DE', 'Germany'), ('DJ', 'Djibouti'), ('DK', 'Denmark'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('DZ', 'Algeria'), ('EC', 'Ecuador'), ('EE', 'Estonia'), ('EG', 'Egypt'), ('EH', 'Western Sahara'), ('ER', 'Eritrea'), ('ES', 'Spain'), ('ET', 'Ethiopia'), ('FI', 'Finland'), ('FJ', 'Fiji'), ('FK', 'Falkland Islands (Malvinas)'), ('FM', 'Micronesia'), ('FO', 'Faroe Islands'), ('FR', 'France'), ('FX', 'France, Metropolitan'), ('GA', 'Gabon'), ('GB', 'United Kingdom (Great Britain)'), ('GD', 'Grenada'), ('GE', 'Georgia'), ('GF', 'French Guiana'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GL', 'Greenland'), ('GM', 'Gambia'), ('GN', 'Guinea'), ('GP', 'Guadeloupe'), ('GQ', 'Equatorial Guinea'), ('GR', 'Greece'), ('GS', 'South Georgia and the South Sandwich Islands'), ('GT', 'Guatemala'), ('GU', 'Guam'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HK', 'Hong Kong'), ('HM', 'Heard & McDonald Islands'), ('HN', 'Honduras'), ('HR', 'Croatia'), ('HT', 'Haiti'), ('HU', 'Hungary'), ('ID', 'Indonesia'), ('IE', 'Ireland'), ('IL', 'Israel'), ('IN', 'India'), ('IO', 'British Indian Ocean Territory'), ('IQ', 'Iraq'), ('IR', 'Islamic Republic of Iran'), ('IS', 'Iceland'), ('IT', 'Italy'), ('JM', 'Jamaica'), ('JO', 'Jordan'), ('JP', 'Japan'), ('KE', 'Kenya'), ('KG', 'Kyrgyzstan'), ('KH', 'Cambodia'), ('KI', 'Kiribati'), ('KM', 'Comoros'), ('KN', 'St. Kitts and Nevis'), ('KP', "Korea, Democratic People's Republic of"), ('KR', 'Korea, Republic of'), ('KW', 'Kuwait'), ('KY', 'Cayman Islands'), ('KZ', 'Kazakhstan'), ('LA', "Lao People's Democratic Republic"), ('LB', 'Lebanon'), ('LC', 'Saint Lucia'), ('LI', 'Liechtenstein'), ('LK', 'Sri Lanka'), ('LR', 'Liberia'), ('LS', 'Lesotho'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('LV', 'Latvia'), ('LY', 'Libyan Arab Jamahiriya'), ('MA', 'Morocco'), ('MC', 'Monaco'), ('MD', 'Moldova, Republic of'), ('MG', 'Madagascar'), ('MH', 'Marshall Islands'), ('ML', 'Mali'), ('MN', 'Mongolia'), ('MM', 'Myanmar'), ('MO', 'Macau'), ('MP', 'Northern Mariana Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MS', 'Monserrat'), ('MT', 'Malta'), ('MU', 'Mauritius'), ('MV', 'Maldives'), ('MW', 'Malawi'), ('MX', 'Mexico'), ('MY', 'Malaysia'), ('MZ', 'Mozambique'), ('NA', 'Namibia'), ('NC', 'New Caledonia'), ('NE', 'Niger'), ('NF', 'Norfolk Island'), ('NG', 'Nigeria'), ('NI', 'Nicaragua'), ('NL', 'Netherlands'), ('NO', 'Norway'), ('NP', 'Nepal'), ('NR', 'Nauru'), ('NU', 'Niue'), ('NZ', 'New Zealand'), ('OM', 'Oman'), ('PA', 'Panama'), ('PE', 'Peru'), ('PF', 'French Polynesia'), ('PG', 'Papua New Guinea'), ('PH', 'Philippines'), ('PK', 'Pakistan'), ('PL', 'Poland'), ('PM', 'St. Pierre & Miquelon'), ('PN', 'Pitcairn'), ('PR', 'Puerto Rico'), ('PT', 'Portugal'), ('PW', 'Palau'), ('PY', 'Paraguay'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russian Federation'), ('RW', 'Rwanda'), ('SA', 'Saudi Arabia'), ('SB', 'Solomon Islands'), ('SC', 'Seychelles'), ('SD', 'Sudan'), ('SE', 'Sweden'), ('SG', 'Singapore'), ('SH', 'St. Helena'), ('SI', 'Slovenia'), ('SJ', 'Svalbard & Jan Mayen Islands'), ('SK', 'Slovakia'), ('SL', 'Sierra Leone'), ('SM', 'San Marino'), ('SN', 'Senegal'), ('SO', 'Somalia'), ('SR', 'Suriname'), ('ST', 'Sao Tome & Principe'), ('SV', 'El Salvador'), ('SY', 'Syrian Arab Republic'), ('SZ', 'Swaziland'), ('TC', 'Turks & Caicos Islands'), ('TD', 'Chad'), ('TF', 'French Southern Territories'), ('TG', 'Togo'), ('TH', 'Thailand'), ('TJ', 'Tajikistan'), ('TK', 'Tokelau'), ('TM', 'Turkmenistan'), ('TN', 'Tunisia'), ('TO', 'Tonga'), ('TP', 'East Timor'), ('TR', 'Turkey'), ('TT', 'Trinidad & Tobago'), ('TV', 'Tuvalu'), ('TW', 'Taiwan, Province of China'), ('TZ', 'Tanzania, United Republic of'), ('UA', 'Ukraine'), ('UG', 'Uganda'), ('UM', 'United States Minor Outlying Islands'), ('US', 'United States of America'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VA', 'Vatican City State (Holy See)'), ('VC', 'St. Vincent & the Grenadines'), ('VE', 'Venezuela'), ('VG', 'British Virgin Islands'), ('VI', 'United States Virgin Islands'), ('VN', 'Viet Nam'), ('VU', 'Vanuatu'), ('WF', 'Wallis & Futuna Islands'), ('WS', 'Samoa'), ('YE', 'Yemen'), ('YT', 'Mayotte'), ('YU', 'Yugoslavia'), ('ZA', 'South Africa'), ('ZM', 'Zambia'), ('ZR', 'Zaire'), ('ZW', 'Zimbabwe')], default='CH', max_length=2), + ), + ] From 70f0fed63f05a7ddc1125965e5b5c14279eae9e4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 14 Dec 2019 10:18:39 +0530 Subject: [PATCH 034/471] Add all_customers management command --- .../management/commands/all_customers.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 datacenterlight/management/commands/all_customers.py diff --git a/datacenterlight/management/commands/all_customers.py b/datacenterlight/management/commands/all_customers.py new file mode 100644 index 00000000..d529d28f --- /dev/null +++ b/datacenterlight/management/commands/all_customers.py @@ -0,0 +1,36 @@ +import json +import logging +import sys + +from django.core.management.base import BaseCommand +from membership.models import CustomUser +from hosting.models import ( + HostingOrder, VMDetail, UserCardDetail, UserHostingKey +) +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = '''Dumps the email addresses of all customers who have a VM''' + + def add_arguments(self, parser): + parser.add_argument('-a', '--all_registered', action='store_true', + help='All registered users') + + def handle(self, *args, **options): + all_registered = options['all_registered'] + all_customers_set = set() + if all_registered: + all_customers = CustomUser.objects.filter( + is_admin=False, validated=True + ) + for customer in all_customers: + all_customers_set.add(customer.email) + print(customer.email) + else: + all_hosting_orders = HostingOrder.objects.all() + for order in all_hosting_orders: + all_customers_set.add(order.customer.user.email) + print(order.customer.user.email) + + print("Total customers = %s" % len(all_customers_set)) From 991908c37ed51ff60beffe4f490e526ed5c8604d Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 14 Dec 2019 10:52:20 +0530 Subject: [PATCH 035/471] Fix obtianing active customers only --- datacenterlight/management/commands/all_customers.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/datacenterlight/management/commands/all_customers.py b/datacenterlight/management/commands/all_customers.py index d529d28f..2a2d6573 100644 --- a/datacenterlight/management/commands/all_customers.py +++ b/datacenterlight/management/commands/all_customers.py @@ -26,11 +26,13 @@ class Command(BaseCommand): ) for customer in all_customers: all_customers_set.add(customer.email) - print(customer.email) else: - all_hosting_orders = HostingOrder.objects.all() + all_hosting_orders = HostingOrder.objects.filter() + running_vm_details = VMDetail.objects.filter(terminated_at=None) + running_vm_ids = [rvm.vm_id for rvm in running_vm_details] for order in all_hosting_orders: - all_customers_set.add(order.customer.user.email) - print(order.customer.user.email) - + if order.vm_id in running_vm_ids: + all_customers_set.add(order.customer.user.email) + for cu in all_customers_set: + print(cu) print("Total customers = %s" % len(all_customers_set)) From 7442cbd9ca5c629a2724fc9c58356df400499f61 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 14 Dec 2019 10:56:14 +0530 Subject: [PATCH 036/471] Print appropriate message --- datacenterlight/management/commands/all_customers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/datacenterlight/management/commands/all_customers.py b/datacenterlight/management/commands/all_customers.py index 2a2d6573..93b89373 100644 --- a/datacenterlight/management/commands/all_customers.py +++ b/datacenterlight/management/commands/all_customers.py @@ -35,4 +35,7 @@ class Command(BaseCommand): all_customers_set.add(order.customer.user.email) for cu in all_customers_set: print(cu) - print("Total customers = %s" % len(all_customers_set)) + if all_registered: + print("All registered users = %s" % len(all_customers_set)) + else: + print("Total active customers = %s" % len(all_customers_set)) From 6666e40ec4430d8bd50d8dce632b5024bbca715a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 14 Dec 2019 11:00:37 +0530 Subject: [PATCH 037/471] Remove unused imports --- datacenterlight/management/commands/all_customers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/datacenterlight/management/commands/all_customers.py b/datacenterlight/management/commands/all_customers.py index 93b89373..adbd8552 100644 --- a/datacenterlight/management/commands/all_customers.py +++ b/datacenterlight/management/commands/all_customers.py @@ -1,12 +1,12 @@ -import json import logging -import sys from django.core.management.base import BaseCommand -from membership.models import CustomUser + from hosting.models import ( - HostingOrder, VMDetail, UserCardDetail, UserHostingKey + HostingOrder, VMDetail ) +from membership.models import CustomUser + logger = logging.getLogger(__name__) From 859249b894dfa15892ada26b3146df9941fdbca1 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 14 Dec 2019 11:20:47 +0530 Subject: [PATCH 038/471] Update Changelog --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 17efb793..c2aed106 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.7.1: 2019-12-14 + * feature: Add management command to list active VM customers (MR!723) 2.7: 2019-12-9 * feature: EU VAT for new subscriptions (MR!721) Notes for deployment: From b52f2de8d7ccd1ae8aa66086ca5ba7478484688b Mon Sep 17 00:00:00 2001 From: meow Date: Sat, 14 Dec 2019 14:29:45 +0500 Subject: [PATCH 039/471] now using hash func from utils.ldap_manager --- dynamicweb/settings/ldap_max_uid_file | 2 +- hosting/views.py | 6 ++++-- membership/models.py | 7 +++---- utils/backend.py | 2 +- utils/ldap_manager.py | 9 +++++---- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/dynamicweb/settings/ldap_max_uid_file b/dynamicweb/settings/ldap_max_uid_file index d3cdc227..6cd35a3e 100644 --- a/dynamicweb/settings/ldap_max_uid_file +++ b/dynamicweb/settings/ldap_max_uid_file @@ -1 +1 @@ -10192 \ No newline at end of file +10200 \ No newline at end of file diff --git a/hosting/views.py b/hosting/views.py index 7ee1b93b..4633748a 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -398,10 +398,12 @@ class PasswordResetConfirmView(HostingContextMixin, if form.is_valid(): ldap_manager = LdapManager() new_password = form.cleaned_data['new_password2'] - user.create_ldap_account() + + user.create_ldap_account(new_password) user.set_password(new_password) user.save() - ldap_manager.change_password(user.username, user.password) + + ldap_manager.change_password(user.username, new_password) messages.success(request, _('Password has been reset.')) # Change opennebula password diff --git a/membership/models.py b/membership/models.py index dd7b1363..5ec6cb6c 100644 --- a/membership/models.py +++ b/membership/models.py @@ -50,7 +50,7 @@ class MyUserManager(BaseUserManager): user.is_admin = False user.set_password(password) user.save(using=self._db) - user.create_ldap_account() + user.create_ldap_account(password) return user def create_superuser(self, email, name, password): @@ -214,7 +214,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): # The user is identified by their email address return self.email - def create_ldap_account(self): + def create_ldap_account(self, password): # create ldap account for user if it does not exists already. if self.in_ldap: return @@ -236,8 +236,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): first_name, last_name = get_first_and_last_name(self.name) if not last_name: last_name = first_name - - ldap_manager.create_user(self.username, password=self.password, + ldap_manager.create_user(self.username, password=password, firstname=first_name, lastname=last_name, email=self.email) self.in_ldap = True diff --git a/utils/backend.py b/utils/backend.py index 485dfe93..cbf38d6c 100644 --- a/utils/backend.py +++ b/utils/backend.py @@ -13,7 +13,7 @@ class MyLDAPBackend(object): # User does not exists in Database return None else: - user.create_ldap_account() + user.create_ldap_account(password) if user.check_password(password): return user else: diff --git a/utils/ldap_manager.py b/utils/ldap_manager.py index ee16937d..fd039ad5 100644 --- a/utils/ldap_manager.py +++ b/utils/ldap_manager.py @@ -58,8 +58,7 @@ class LdapManager: SALT_BYTES = 15 sha1 = hashlib.sha1() - salt = self.rng.getrandbits(SALT_BYTES * 8).to_bytes(SALT_BYTES, - "little") + salt = self.rng.getrandbits(SALT_BYTES * 8).to_bytes(SALT_BYTES, "little") sha1.update(password) sha1.update(salt) @@ -104,7 +103,9 @@ class LdapManager: "loginShell": ["/bin/bash"], "homeDirectory": ["/home/{}".format(user).encode("utf-8")], "mail": email.encode("utf-8"), - "userPassword": [password.encode("utf-8")] + "userPassword": [self._ssha_password( + password.encode("utf-8") + )] } ) logger.debug('Created user %s %s' % (user.encode('utf-8'), @@ -139,7 +140,7 @@ class LdapManager: { "userpassword": ( ldap3.MODIFY_REPLACE, - [new_password.encode("utf-8")] + [self._ssha_password(new_password.encode("utf-8"))] ) } ) From eda766dc6c215a4eda16a368d2c1b8d37fce8e50 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 14 Dec 2019 19:19:23 +0530 Subject: [PATCH 040/471] Check if we get correct opennebula user id --- opennebula_api/models.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index f8ef6481..31b8955d 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -485,11 +485,17 @@ class OpenNebulaManager(): ) def change_user_password(self, passwd_hash): - self.oneadmin_client.call( - oca.User.METHODS['passwd'], - self.opennebula_user.id, - passwd_hash - ) + if type(self.opennebula_user) == int: + logger.debug("opennebula_user is int and has value = %s" % + self.opennebula_user) + else: + logger.debug("opennebula_user is object and corresponding id is %s" + % self.opennebula_user.id) + # self.oneadmin_client.call( + # oca.User.METHODS['passwd'], + # self.opennebula_user if type(self.opennebula_user) == int else self.opennebula_user.id, + # passwd_hash + # ) def add_public_key(self, user, public_key='', merge=False): """ From 9c96f2447c1ddc6d4a824552548d661b8c5ebda0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 14 Dec 2019 19:47:01 +0530 Subject: [PATCH 041/471] Uncomment password change call --- opennebula_api/models.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 31b8955d..19e3e4f7 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -491,11 +491,11 @@ class OpenNebulaManager(): else: logger.debug("opennebula_user is object and corresponding id is %s" % self.opennebula_user.id) - # self.oneadmin_client.call( - # oca.User.METHODS['passwd'], - # self.opennebula_user if type(self.opennebula_user) == int else self.opennebula_user.id, - # passwd_hash - # ) + self.oneadmin_client.call( + oca.User.METHODS['passwd'], + self.opennebula_user if type(self.opennebula_user) == int else self.opennebula_user.id, + passwd_hash + ) def add_public_key(self, user, public_key='', merge=False): """ From c1137c26a166a1148a9e66e23fcf58a782991ea8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 14 Dec 2019 19:48:48 +0530 Subject: [PATCH 042/471] Don't track ldap max uid file --- dynamicweb/settings/ldap_max_uid_file | 1 - 1 file changed, 1 deletion(-) delete mode 100644 dynamicweb/settings/ldap_max_uid_file diff --git a/dynamicweb/settings/ldap_max_uid_file b/dynamicweb/settings/ldap_max_uid_file deleted file mode 100644 index 6cd35a3e..00000000 --- a/dynamicweb/settings/ldap_max_uid_file +++ /dev/null @@ -1 +0,0 @@ -10200 \ No newline at end of file From f9a9a24516cc8636112cc966311e613954490855 Mon Sep 17 00:00:00 2001 From: meow Date: Mon, 16 Dec 2019 12:54:59 +0500 Subject: [PATCH 043/471] Show username in navbar and setting. Show greeting in dashboard for user's name --- hosting/static/hosting/css/dashboard.css | 1 - hosting/static/hosting/css/virtual-machine.css | 11 +++++++++++ hosting/templates/hosting/dashboard.html | 3 +++ hosting/templates/hosting/includes/_navbar_user.html | 2 +- hosting/templates/hosting/settings.html | 4 ++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/hosting/static/hosting/css/dashboard.css b/hosting/static/hosting/css/dashboard.css index c7bbecd9..0b718178 100644 --- a/hosting/static/hosting/css/dashboard.css +++ b/hosting/static/hosting/css/dashboard.css @@ -23,7 +23,6 @@ .hosting-dashboard .dashboard-container-head { color: #fff; - margin-bottom: 60px; } .hosting-dashboard-item { diff --git a/hosting/static/hosting/css/virtual-machine.css b/hosting/static/hosting/css/virtual-machine.css index 726b0f35..4d490ff7 100644 --- a/hosting/static/hosting/css/virtual-machine.css +++ b/hosting/static/hosting/css/virtual-machine.css @@ -248,6 +248,9 @@ .dashboard-title-thin { font-size: 22px; } + .dashboard-greetings-thin { + font-size: 16px; + } } .btn-vm-invoice { @@ -315,6 +318,11 @@ font-size: 32px; } +.dashboard-greetings-thin { + font-weight: 300; + font-size: 24px; +} + .dashboard-title-thin .un-icon { height: 34px; margin-right: 5px; @@ -411,6 +419,9 @@ .dashboard-title-thin { font-size: 22px; } + .dashboard-greetings-thin { + font-size: 16px; + } .dashboard-title-thin .un-icon { height: 22px; width: 22px; diff --git a/hosting/templates/hosting/dashboard.html b/hosting/templates/hosting/dashboard.html index 35ee9b6e..f87c3f61 100644 --- a/hosting/templates/hosting/dashboard.html +++ b/hosting/templates/hosting/dashboard.html @@ -7,6 +7,9 @@

{% trans "My Dashboard" %}

+
+ {% trans "Welcome" %} {{request.user.name}} +

{% trans "Create VM" %}

diff --git a/hosting/templates/hosting/includes/_navbar_user.html b/hosting/templates/hosting/includes/_navbar_user.html index 7362f447..bd77eb5c 100644 --- a/hosting/templates/hosting/includes/_navbar_user.html +++ b/hosting/templates/hosting/includes/_navbar_user.html @@ -26,7 +26,7 @@
diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index f47905a5..2705143c 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -104,15 +104,17 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) ) - vat = price * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) + discount_name = pricing.discount_name + discount_amount = round(float(pricing.discount_amount), 2) + vat = (price - discount_amount) * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) vat_percent = vat_rate cents = decimal.Decimal('.01') price = price.quantize(cents, decimal.ROUND_HALF_UP) vat = vat.quantize(cents, decimal.ROUND_HALF_UP) discount = { - 'name': pricing.discount_name, - 'amount': round(float(pricing.discount_amount), 2) + 'name': discount_name, + 'amount': discount_amount } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) From 112f3e17a9ce7d2f998546e7690261c2a000225b Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 24 Jan 2020 15:03:07 +0530 Subject: [PATCH 197/471] Convert to decimal for type consistency --- utils/hosting_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 2705143c..3312474d 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -106,7 +106,7 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, discount_name = pricing.discount_name discount_amount = round(float(pricing.discount_amount), 2) - vat = (price - discount_amount) * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) + vat = (price - decimal.Decimal(discount_amount)) * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) vat_percent = vat_rate cents = decimal.Decimal('.01') From 156930ab26418f260967cf5cf60f250cb27f81ba Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 24 Jan 2020 15:24:46 +0530 Subject: [PATCH 198/471] Add price_after_discount explicitly for showing in template --- datacenterlight/templates/datacenterlight/order_detail.html | 2 +- datacenterlight/views.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 80e35914..c2b97556 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -138,7 +138,7 @@ {% endif %}

{% trans "Subtotal" %} - {{vm.price - vm.discount.amount|floatformat:2|intcomma}} CHF + {{vm.price_after_discount}} CHF

{% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%) : diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 92c65681..8c1fe504 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -642,6 +642,7 @@ class OrderConfirmationView(DetailView, FormView): vat_rate=user_country_vat_rate * 100 ) vm_specs["price"] = price + vm_specs["price_after_discount"] = price - discount vat_number = request.session.get('billing_address_data').get("vat_number") billing_address = BillingAddress.objects.get(id=request.session["billing_address_id"]) From d1fd57b7308b60ab8e82ba56bfcd44823ce0d724 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 24 Jan 2020 15:26:41 +0530 Subject: [PATCH 199/471] Correct the way we get amount from discount --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 8c1fe504..b2dec576 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -642,7 +642,7 @@ class OrderConfirmationView(DetailView, FormView): vat_rate=user_country_vat_rate * 100 ) vm_specs["price"] = price - vm_specs["price_after_discount"] = price - discount + vm_specs["price_after_discount"] = price - discount["amount"] vat_number = request.session.get('billing_address_data').get("vat_number") billing_address = BillingAddress.objects.get(id=request.session["billing_address_id"]) From cde6c51d4b22a3b8ada0a4362c8d17daff0847d7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 26 Jan 2020 10:06:31 +0530 Subject: [PATCH 200/471] Reset vat_validation_status when on payment page --- datacenterlight/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index b2dec576..0d62cf1e 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -309,6 +309,7 @@ class PaymentOrderView(FormView): @cache_control(no_cache=True, must_revalidate=True, no_store=True) def get(self, request, *args, **kwargs): + request.session.pop('vat_validation_status') if (('type' in request.GET and request.GET['type'] == 'generic') or 'product_slug' in kwargs): request.session['generic_payment_type'] = 'generic' From 24740438f7f05eff23561454928a3eb06faa3044 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:02:26 +0530 Subject: [PATCH 201/471] get_vm_price_for_given_vat: Also return discount amount with vat --- utils/hosting_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 3312474d..61f849ad 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -114,7 +114,8 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, vat = vat.quantize(cents, decimal.ROUND_HALF_UP) discount = { 'name': discount_name, - 'amount': discount_amount + 'amount': discount_amount, + 'amount_with_vat': round(discount_amount * vat_rate, 2) } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) From 970834cc38e77a2d3e3a1c9275e5cb677a157fc3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:03:13 +0530 Subject: [PATCH 202/471] Add parameters needed for displaying prices after discount, and vat --- datacenterlight/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 0d62cf1e..f1911cbf 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -681,6 +681,9 @@ class OrderConfirmationView(DetailView, FormView): 2) vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount + vm_specs["price_with_vat"] = price + vat + vm_specs["price_after_discount"] = round(price - discount['amount'], 2) + vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) + vm_specs["vat"], 2) request.session['specs'] = vm_specs context.update({ From b8eca59e0dbfdae39daf2b6f0420735eda832116 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:04:03 +0530 Subject: [PATCH 203/471] Fix design for showing prices excl and incl vat and discount --- .../datacenterlight/order_detail.html | 127 +++++++++++++----- 1 file changed, 94 insertions(+), 33 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index c2b97556..f62fd063 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -121,40 +121,101 @@


- {% if vm.vat > 0 or vm.discount.amount > 0 %} -
-
- {% if vm.vat > 0 %} -

- {% trans "Price" %} - {{vm.price|floatformat:2|intcomma}} CHF -

- {% if vm.discount.amount > 0 %} -

- {%trans "Discount" as discount_name %} - {{ vm.discount.name|default:discount_name }} - - {{ vm.discount.amount }} CHF -

- {% endif %} -

- {% trans "Subtotal" %} - {{vm.price_after_discount}} CHF -

-

- {% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%) : - {{vm.vat|floatformat:2|intcomma}} CHF -

- {% endif %} -
-
-
-
-
- {% endif %}
-

- {% trans "Total" %} - {{vm.total_price|floatformat:2|intcomma}} CHF +

+ {% trans "Price Before VAT" %} + {{vm.price|intcomma}} CHF +

+
+
+
+
+
+ +
+
+

+
+
+

Pre VAT

+
+
+

VAT for {{vm.vat_country}} ({{vm.vat_percent}}%)

+
+
+
+
+

Price

+
+
+

{{vm.price|intcomma}} CHF

+
+
+

{{vm.price_with_vat|intcomma}} CHF

+
+
+ {% if vm.discount.amount > 0 %} +
+
+

{{vm.discount.name}}

+
+
+

-{{vm.discount.amount|intcomma}} CHF

+
+
+

-{{vm.discount.amount_with_vat|intcomma}} CHF

+
+
+ {% endif %} +
+
+
+
+
+
+
+

Total

+
+
+

{{vm.price_after_discount|intcomma}} CHF

+
+
+

{{vm.price_after_discount_with_vat|intcomma}} CHF

+
+
+
+
+
+
+
+

+ {% trans "Your Price in Total" %} + {{vm.total_price|floatformat:2|intcomma}} CHF

From 1f79ccd490ac9734d7f40508ec88da2effb545a9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:15:28 +0530 Subject: [PATCH 204/471] Convert discount amount into CHF --- utils/hosting_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 61f849ad..85829d05 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -115,7 +115,7 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, discount = { 'name': discount_name, 'amount': discount_amount, - 'amount_with_vat': round(discount_amount * vat_rate, 2) + 'amount_with_vat': round(discount_amount * vat_rate * 0.01, 2) } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) From 48cc2b49394c97433ed2d435490b38edc908b1f2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:19:48 +0530 Subject: [PATCH 205/471] Fix discount amount calculation after VAT --- utils/hosting_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 85829d05..e20e9b16 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -115,7 +115,7 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, discount = { 'name': discount_name, 'amount': discount_amount, - 'amount_with_vat': round(discount_amount * vat_rate * 0.01, 2) + 'amount_with_vat': round(discount_amount * (1 + vat_rate * 0.01), 2) } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) From 6132638faa2e438196ce12f20a6f2fbdfa563da2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:32:07 +0530 Subject: [PATCH 206/471] Use correct vat --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index f1911cbf..f8f7f928 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -681,7 +681,7 @@ class OrderConfirmationView(DetailView, FormView): 2) vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount - vm_specs["price_with_vat"] = price + vat + vm_specs["price_with_vat"] = price + vm_specs["vat"] vm_specs["price_after_discount"] = round(price - discount['amount'], 2) vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) + vm_specs["vat"], 2) request.session['specs'] = vm_specs From a81fdc8ec1a344f92a665a795e5088237b9781f9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:45:07 +0530 Subject: [PATCH 207/471] Revert back to original vat calculation --- utils/hosting_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index e20e9b16..da2540d6 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -106,7 +106,7 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, discount_name = pricing.discount_name discount_amount = round(float(pricing.discount_amount), 2) - vat = (price - decimal.Decimal(discount_amount)) * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) + vat = price * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) vat_percent = vat_rate cents = decimal.Decimal('.01') From ad606c2c554a854e1499b7ec86b04d851f956e91 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:50:05 +0530 Subject: [PATCH 208/471] Correct calculation of total price --- datacenterlight/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index f8f7f928..6dec71d2 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -677,13 +677,13 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["vat"] = vat vm_specs["vat_percent"] = vat_percent vm_specs["vat_validation_status"] = request.session["vat_validation_status"] if "vat_validation_status" in request.session else "" - vm_specs["total_price"] = round(price + vm_specs["vat"] - discount['amount'], + vm_specs["total_price"] = round(price + vm_specs["vat"] - discount['amount_with_vat'], 2) vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount vm_specs["price_with_vat"] = price + vm_specs["vat"] vm_specs["price_after_discount"] = round(price - discount['amount'], 2) - vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) + vm_specs["vat"], 2) + vm_specs["price_after_discount_with_vat"] = round((price - discount['amount_with_vat']) + vm_specs["vat"], 2) request.session['specs'] = vm_specs context.update({ From 8ee4081f6011af7c4506c42ec7c3b5b41c611a1e Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:58:47 +0530 Subject: [PATCH 209/471] Fix round up calculations --- utils/hosting_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index da2540d6..5d898b0a 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -112,10 +112,12 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, cents = decimal.Decimal('.01') price = price.quantize(cents, decimal.ROUND_HALF_UP) vat = vat.quantize(cents, decimal.ROUND_HALF_UP) + discount_amount_with_vat = decimal.Decimal(discount_amount) * (1 + decimal.Decimal(vat_rate) * decimal.Decimal(0.01)) + discount_amount_with_vat = discount_amount_with_vat.quantize(cents, decimal.ROUND_HALF_UP) discount = { 'name': discount_name, 'amount': discount_amount, - 'amount_with_vat': round(discount_amount * (1 + vat_rate * 0.01), 2) + 'amount_with_vat': round(float(discount_amount_with_vat), 2) } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) From 4128aeb64da8a964e9d0794fc0b9d095740fdd00 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 17:21:59 +0530 Subject: [PATCH 210/471] Add line height for final price --- datacenterlight/static/datacenterlight/css/hosting.css | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/static/datacenterlight/css/hosting.css b/datacenterlight/static/datacenterlight/css/hosting.css index 0f16ab77..bcf266cc 100644 --- a/datacenterlight/static/datacenterlight/css/hosting.css +++ b/datacenterlight/static/datacenterlight/css/hosting.css @@ -532,6 +532,7 @@ .order-detail-container .total-price { font-size: 18px; + line-height: 20px; } @media (max-width: 767px) { From f546c5cb4f032b7c071b50aee328c6b224cd27b8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 17:38:44 +0530 Subject: [PATCH 211/471] Increase content space in order detail desktop view --- .../templates/datacenterlight/order_detail.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index f62fd063..fbe6ef16 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -104,7 +104,7 @@ {{ request.session.template.name }}

-
+

{% trans "Cores" %}: {{vm.cpu|floatformat}} @@ -121,7 +121,7 @@


-
+

{% trans "Price Before VAT" %} {{vm.price|intcomma}} CHF @@ -130,7 +130,7 @@


-
+
-
+

{% trans "Cores" %}: {{vm.cpu|floatformat}} @@ -121,7 +132,7 @@


-
+

{% trans "Price Before VAT" %} {{vm.price|intcomma}} CHF @@ -130,64 +141,38 @@


-
- +
-
+

-
-

Pre VAT

+
+

{% trans "Pre VAT" %}

-
-

VAT for {{vm.vat_country}} ({{vm.vat_percent}}%)

+
+

{% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%)

-
+

Price

-
+

{{vm.price|intcomma}} CHF

-
+

{{vm.price_with_vat|intcomma}} CHF

{% if vm.discount.amount > 0 %}
-
+

{{vm.discount.name}}

-
+

-{{vm.discount.amount|intcomma}} CHF

-
+

-{{vm.discount.amount_with_vat|intcomma}} CHF

@@ -196,15 +181,15 @@

-
+
-
+

Total

-
+

{{vm.price_after_discount|intcomma}} CHF

-
+

{{vm.price_after_discount_with_vat|intcomma}} CHF

@@ -212,11 +197,9 @@

-
-

+

{% trans "Your Price in Total" %} {{vm.total_price|floatformat:2|intcomma}} CHF -

{% endif %} From 3141dc279326b38cd0025f080b95ec558ea3ebd8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 30 Jan 2020 11:52:29 +0530 Subject: [PATCH 213/471] Round price_with_vat --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 6dec71d2..2f411194 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -681,7 +681,7 @@ class OrderConfirmationView(DetailView, FormView): 2) vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount - vm_specs["price_with_vat"] = price + vm_specs["vat"] + vm_specs["price_with_vat"] = round(price + vm_specs["vat"], 2) vm_specs["price_after_discount"] = round(price - discount['amount'], 2) vm_specs["price_after_discount_with_vat"] = round((price - discount['amount_with_vat']) + vm_specs["vat"], 2) request.session['specs'] = vm_specs From 7a9b315e2ed0de42eac0f4dc1199e3ace02abe92 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 30 Jan 2020 12:33:47 +0530 Subject: [PATCH 214/471] Add media query for device width less than 368px --- .../templates/datacenterlight/order_detail.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 3236ffe0..525a8a95 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -113,6 +113,15 @@ } } + @media screen and (max-width:367px){ + .cmf-ord-heading { + font-size: 13px; + } + .order-detail-container .order-details { + font-size: 12px; + } + } +
From f4393426d376e73ab13bb3b0cb8602cf5ef731a8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 08:53:24 +0530 Subject: [PATCH 215/471] Fix proper VAT values --- datacenterlight/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 2f411194..8da3e5c5 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -680,10 +680,11 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["total_price"] = round(price + vm_specs["vat"] - discount['amount_with_vat'], 2) vm_specs["vat_country"] = user_vat_country - vm_specs["discount"] = discount - vm_specs["price_with_vat"] = round(price + vm_specs["vat"], 2) + vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"]), 2) vm_specs["price_after_discount"] = round(price - discount['amount'], 2) - vm_specs["price_after_discount_with_vat"] = round((price - discount['amount_with_vat']) + vm_specs["vat"], 2) + vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"]), 2) + discount["amount_with_vat"] = vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"] + vm_specs["discount"] = discount request.session['specs'] = vm_specs context.update({ From b1acd3f25b716851ea4f0fd01a60af70f5756c9a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 09:02:17 +0530 Subject: [PATCH 216/471] Convert VAT percent to CHF before calculations --- datacenterlight/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 8da3e5c5..943c3565 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -680,10 +680,10 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["total_price"] = round(price + vm_specs["vat"] - discount['amount_with_vat'], 2) vm_specs["vat_country"] = user_vat_country - vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"]), 2) + vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"] * 0.01), 2) vm_specs["price_after_discount"] = round(price - discount['amount'], 2) - vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"]), 2) - discount["amount_with_vat"] = vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"] + vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) + discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) vm_specs["discount"] = discount request.session['specs'] = vm_specs From 838163bd5953cdfde64f89bedd42db22bc5f9c56 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 09:15:12 +0530 Subject: [PATCH 217/471] Make total price consistent --- datacenterlight/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 943c3565..37635904 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -677,13 +677,12 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["vat"] = vat vm_specs["vat_percent"] = vat_percent vm_specs["vat_validation_status"] = request.session["vat_validation_status"] if "vat_validation_status" in request.session else "" - vm_specs["total_price"] = round(price + vm_specs["vat"] - discount['amount_with_vat'], - 2) vm_specs["vat_country"] = user_vat_country vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"] * 0.01), 2) vm_specs["price_after_discount"] = round(price - discount['amount'], 2) vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) + vm_specs["total_price"] = round(vm_specs["price_after_discount_with_vat"]) vm_specs["discount"] = discount request.session['specs'] = vm_specs From e6f00abd71c59dc788c88adb5e17d181dd3a443c Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 09:17:24 +0530 Subject: [PATCH 218/471] Do not round --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 37635904..4307fcc3 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -682,7 +682,7 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["price_after_discount"] = round(price - discount['amount'], 2) vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) - vm_specs["total_price"] = round(vm_specs["price_after_discount_with_vat"]) + vm_specs["total_price"] = vm_specs["price_after_discount_with_vat"] vm_specs["discount"] = discount request.session['specs'] = vm_specs From 918d2b17e1d476f2311fc9d6f6af5960c2272f57 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 12:13:56 +0530 Subject: [PATCH 219/471] Change invoice url --- .../hosting/virtual_machine_detail.html | 2 +- hosting/views.py | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index 5873a2aa..36c04fed 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -46,7 +46,7 @@
{% trans "Current Pricing" %}
{{order.price|floatformat:2|intcomma}} CHF/{% if order.generic_product %}{% trans order.generic_product.product_subscription_interval %}{% else %}{% trans "Month" %}{% endif %}
- {% trans "See Invoice" %} + {% trans "See Invoice" %}
diff --git a/hosting/views.py b/hosting/views.py index 672868d8..b7bf07c8 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1687,13 +1687,27 @@ class VirtualMachineView(LoginRequiredMixin, View): context = None try: serializer = VirtualMachineSerializer(vm) + hosting_order = HostingOrder.objects.get( + vm_id=serializer.data['vm_id'] + ) + inv_url = None + if hosting_order.subscription_id: + stripe_obj = stripe.Invoice.list( + hosting_order.subscription_id + ) + inv_url = stripe_obj[0].data.hosted_invoice_url + elif hosting_order.stripe_charge_id: + stripe_obj = stripe.Charge.retrieve( + hosting_order.stripe_charge_id + ) + inv_url = stripe_obj.receipt_url context = { 'virtual_machine': serializer.data, - 'order': HostingOrder.objects.get( - vm_id=serializer.data['vm_id'] - ), - 'keys': UserHostingKey.objects.filter(user=request.user) + 'order': hosting_order, + 'keys': UserHostingKey.objects.filter(user=request.user), + 'inv_url': inv_url } + except Exception as ex: logger.debug("Exception generated {}".format(str(ex))) messages.error(self.request, From d8482c52f9d7d487ad1dcab7fa462686020db482 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 12:20:13 +0530 Subject: [PATCH 220/471] Get invoice correctly --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index b7bf07c8..1f30ba11 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1693,7 +1693,7 @@ class VirtualMachineView(LoginRequiredMixin, View): inv_url = None if hosting_order.subscription_id: stripe_obj = stripe.Invoice.list( - hosting_order.subscription_id + subscription=hosting_order.subscription_id ) inv_url = stripe_obj[0].data.hosted_invoice_url elif hosting_order.stripe_charge_id: From 9d21181073748d60b1f85759ce44e03a07324215 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 12:23:47 +0530 Subject: [PATCH 221/471] Get stripe invoice obj correctly --- hosting/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 1f30ba11..09e1131a 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1693,9 +1693,10 @@ class VirtualMachineView(LoginRequiredMixin, View): inv_url = None if hosting_order.subscription_id: stripe_obj = stripe.Invoice.list( - subscription=hosting_order.subscription_id + subscription=hosting_order.subscription_id, + count=0 ) - inv_url = stripe_obj[0].data.hosted_invoice_url + inv_url = stripe_obj.data[0].hosted_invoice_url elif hosting_order.stripe_charge_id: stripe_obj = stripe.Charge.retrieve( hosting_order.stripe_charge_id From b645f9894bbc0b67ac800353ed5f266fd438412a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 12:25:51 +0530 Subject: [PATCH 222/471] Open invoice in a new window --- hosting/templates/hosting/virtual_machine_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index 36c04fed..24b2c6ad 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -46,7 +46,7 @@
{% trans "Current Pricing" %}
{{order.price|floatformat:2|intcomma}} CHF/{% if order.generic_product %}{% trans order.generic_product.product_subscription_interval %}{% else %}{% trans "Month" %}{% endif %}
- {% trans "See Invoice" %} + {% trans "See Invoice" %}
From 23b25002aeb713604c8b47e9814e5a8282bdc8d9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 13:00:04 +0530 Subject: [PATCH 223/471] Take users to invoice url instead of orders Orders need a VAT alignment fix --- datacenterlight/tasks.py | 3 +- .../datacenterlight/order_detail.html | 36 +++++++++---------- datacenterlight/views.py | 2 +- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 8b4626e8..55be8099 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -173,8 +173,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): context = { 'base_url': "{0}://{1}".format(user.get('request_scheme'), user.get('request_host')), - 'order_url': reverse('hosting:orders', - kwargs={'pk': order_id}), + 'order_url': reverse('hosting:invoices'), 'page_header': _( 'Your New VM %(vm_name)s at Data Center Light') % { 'vm_name': vm.get('name')}, diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 525a8a95..6f9d43f2 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -103,26 +103,26 @@ {% trans "Product" %}:  {{ request.session.template.name }}

- +

diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 4307fcc3..385cf808 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1154,7 +1154,7 @@ class OrderConfirmationView(DetailView, FormView): response = { 'status': True, 'redirect': ( - reverse('hosting:orders') + reverse('hosting:invoices') if request.user.is_authenticated() else reverse('datacenterlight:index') ), From 2058c660c0ea364bfb329255bc2ac5601b81204a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 13:15:03 +0530 Subject: [PATCH 224/471] Retrieve only one invoice --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 09e1131a..729d115b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1694,7 +1694,7 @@ class VirtualMachineView(LoginRequiredMixin, View): if hosting_order.subscription_id: stripe_obj = stripe.Invoice.list( subscription=hosting_order.subscription_id, - count=0 + count=1 ) inv_url = stripe_obj.data[0].hosted_invoice_url elif hosting_order.stripe_charge_id: From c43afb7c590dccf0ce9e7b8b283d418f3869440b Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 13:47:29 +0530 Subject: [PATCH 225/471] Use round_up method --- datacenterlight/views.py | 10 +++++----- utils/hosting_utils.py | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 385cf808..d6bfd27d 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -29,7 +29,7 @@ from utils.forms import ( ) from utils.hosting_utils import ( get_vm_price_with_vat, get_all_public_keys, get_vat_rate_for_country, - get_vm_price_for_given_vat + get_vm_price_for_given_vat, round_up ) from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task @@ -678,10 +678,10 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["vat_percent"] = vat_percent vm_specs["vat_validation_status"] = request.session["vat_validation_status"] if "vat_validation_status" in request.session else "" vm_specs["vat_country"] = user_vat_country - vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"] * 0.01), 2) - vm_specs["price_after_discount"] = round(price - discount['amount'], 2) - vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) - discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) + vm_specs["price_with_vat"] = round_up(price * (1 + vm_specs["vat_percent"] * 0.01), 2) + vm_specs["price_after_discount"] = round_up(price - discount['amount'], 2) + vm_specs["price_after_discount_with_vat"] = round_up((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) + discount["amount_with_vat"] = round_up(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) vm_specs["total_price"] = vm_specs["price_after_discount_with_vat"] vm_specs["discount"] = discount request.session['specs'] = vm_specs diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 5d898b0a..7bff9a89 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -1,5 +1,6 @@ import decimal import logging +import math import subprocess from oca.pool import WrongIdError @@ -214,6 +215,11 @@ def get_ip_addresses(vm_id): return "--" +def round_up(n, decimals=0): + multiplier = 10 ** decimals + return math.ceil(n * multiplier) / multiplier + + class HostingUtils: @staticmethod def clear_items_from_list(from_list, items_list): From b103cff0a6742bea0a178914985ae7f29882e1db Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 13:54:29 +0530 Subject: [PATCH 226/471] Dont round_up discount (is obtained from subtraction) --- datacenterlight/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index d6bfd27d..185b3e29 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -681,9 +681,10 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["price_with_vat"] = round_up(price * (1 + vm_specs["vat_percent"] * 0.01), 2) vm_specs["price_after_discount"] = round_up(price - discount['amount'], 2) vm_specs["price_after_discount_with_vat"] = round_up((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) - discount["amount_with_vat"] = round_up(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) + discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) vm_specs["total_price"] = vm_specs["price_after_discount_with_vat"] vm_specs["discount"] = discount + logger.debug(vm_specs) request.session['specs'] = vm_specs context.update({ From 8fe689d9933ae6c24c29df12b9b52b284ada2db0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 15:23:26 +0530 Subject: [PATCH 227/471] Update some DE translations --- .../locale/de/LC_MESSAGES/django.po | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index ebb78a1c..5ba12558 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: 2019-12-09 12:13+0000\n" +"POT-Creation-Date: 2020-02-01 09:42+0000\n" "PO-Revision-Date: 2018-03-30 23:22+0000\n" "Last-Translator: b'Anonymous User '\n" "Language-Team: LANGUAGE \n" @@ -144,7 +144,6 @@ 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!" @@ -407,6 +406,17 @@ msgstr "Datum" msgid "Billed to" msgstr "Rechnungsadresse" +msgid "VAT Number" +msgstr "Mehrwertsteuernummer" + +msgid "Your VAT number has been verified" +msgstr "Deine Mehrwertsteuernummer wurde überprüft" + +msgid "" +"Your VAT number is under validation. VAT will be adjusted, once the " +"validation is complete." +msgstr "" + msgid "Payment method" msgstr "Bezahlmethode" @@ -437,8 +447,14 @@ msgstr "Beschreibung" msgid "Recurring" msgstr "Wiederholend" -msgid "Subtotal" -msgstr "Zwischensumme" +msgid "Price Before VAT" +msgstr "" + +msgid "Pre VAT" +msgstr "" + +msgid "Your Price in Total" +msgstr "" #, fuzzy, python-format #| msgid "" @@ -575,6 +591,9 @@ msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!" 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 "" + msgid "Invalid number of cores" msgstr "Ungültige Anzahle CPU-Kerne" @@ -665,6 +684,9 @@ msgstr "" "Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " "auf sie zugreifen kannst." +#~ msgid "Subtotal" +#~ msgstr "Zwischensumme" + #~ msgid "VAT" #~ msgstr "Mehrwertsteuer" @@ -676,9 +698,6 @@ msgstr "" #~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt " #~ "hast." -#~ msgid "Card Number" -#~ msgstr "Kreditkartennummer" - #~ msgid "" #~ "You are not making any payment yet. After placing your order, you will be " #~ "taken to the Submit Payment Page." From 88a39ef85a6f9250900a28b0f1156a2fe51cc242 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 15:23:42 +0530 Subject: [PATCH 228/471] Update Changelog for 2.10 --- Changelog | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Changelog b/Changelog index 566f5960..2c2d73d7 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,8 @@ +2.10: 2020-02-01 + * Feature: Introduce new design to show VAT exclusive/VAT inclusive pricing together + * Feature: Separate VAT and discount in Stripe + * Feature: Show Stripe invoices until we have a better way of showing them elegantly + * Bugfix: Fix bug where VAT is set to 0 because user set a valid VAT number before but later chose not to use any VAT number 2.9.5: 2020-01-20 * Feature: Show invoices directly from stripe (MR!727) 2.9.4: 2020-01-10 From 44dee625b4f0f5099a7ffd8d0dc43520ffe4069c Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 20:24:55 +0530 Subject: [PATCH 229/471] Add some DE translations --- datacenterlight/locale/de/LC_MESSAGES/django.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index 5ba12558..2c9d0587 100644 --- a/datacenterlight/locale/de/LC_MESSAGES/django.po +++ b/datacenterlight/locale/de/LC_MESSAGES/django.po @@ -407,10 +407,10 @@ 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 " @@ -451,7 +451,7 @@ msgid "Price Before VAT" msgstr "" msgid "Pre VAT" -msgstr "" +msgstr "Exkl. MwSt." msgid "Your Price in Total" msgstr "" From c765698a0f96618ee283fa309fa34b82f4e63d9e Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 10:16:45 +0530 Subject: [PATCH 230/471] Add two columns for pricing --- .../datacenterlight/order_detail.html | 133 +++++++++++------- 1 file changed, 86 insertions(+), 47 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 6f9d43f2..80c5f459 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -58,52 +58,7 @@


{% 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 "VAT for" %} {{generic_payment_details.vat_country}} ({{generic_payment_details.vat_rate}}%) : - CHF {{generic_payment_details.vat_amount|floatformat:2|intcomma}} -

-

- {% trans "Total Amount" %} : - CHF {{generic_payment_details.amount|floatformat:2|intcomma}} -

- {% else %} -

- {% trans "Amount" %}: - CHF {{generic_payment_details.amount|floatformat:2|intcomma}} -

- {% endif %} - {% if generic_payment_details.description %} -

- {% trans "Description" %}: - {{generic_payment_details.description}} -

- {% endif %} - {% if generic_payment_details.recurring %} -

- {% trans "Recurring" %}: - Yes -

- {% endif %} -
-
- {% else %} -

- {% trans "Product" %}:  - {{ request.session.template.name }} -

- + + {% if generic_payment_details %} +

+ {% trans "Product" %}:  + {{ generic_payment_details.product_name }} +

+ {% if generic_payment_details.description %} +

+ {% trans "Description" %}: + {{generic_payment_details.description}} +

+ {% endif %} + {% if generic_payment_details.recurring %} +

+ {% trans "Recurring" %}: + Yes +

+ {% 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_percent}}%)

+
+
+
+
+

Price

+
+
+

{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF

+
+
+

{{generic_payment_details.amount|floatformat:2|intcomma}} CHF

+
+
+
+
+
+
+
+
+
+

Total

+
+
+

{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF

+
+
+

{{generic_payment_details.amount|floatformat:2|intcomma}} CHF

+
+
+
+
+
+
+
+ {% trans "Your Price in Total" %} + {{generic_payment_details.amount|floatformat:2|intcomma}} CHF +
+
+ {% else %} +

+ {% trans "Product" %}:  + {{ request.session.template.name }} +

From ffde015c3124eb87713ee61ba45644054fd7e14f Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 10:26:13 +0530 Subject: [PATCH 231/471] Fix divs --- .../datacenterlight/order_detail.html | 113 +++++++++--------- 1 file changed, 55 insertions(+), 58 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 80c5f459..3e2ecf04 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -95,68 +95,65 @@ Yes

{% 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}}%)

+
-
-

- {% trans "Price Before VAT" %} - {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF -

+
+
+

Price

+
+
+

{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF

+
+
+

{{generic_payment_details.amount|floatformat:2|intcomma}} CHF

+
-
-
-
-
-
-
-

-
-
-

{% trans "Pre VAT" %}

-
-
-

{% trans "VAT for" %} {{generic_payment_details.vat_country}} ({{generic_payment_details.vat_percent}}%)

-
-
-
-
-

Price

-
-
-

{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF

-
-
-

{{generic_payment_details.amount|floatformat:2|intcomma}} CHF

-
-
-
-
-
-
-
-
-
-

Total

-
-
-

{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF

-
-
-

{{generic_payment_details.amount|floatformat:2|intcomma}} CHF

-
-
-
-
-
-
-
- {% trans "Your Price in Total" %} - {{generic_payment_details.amount|floatformat:2|intcomma}} CHF +
+
+
+
+
+
+
+

Total

+
+
+

{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF

+
+
+

{{generic_payment_details.amount|floatformat:2|intcomma}} CHF

+
+
+
+
+
+ {% trans "Your Price in Total" %} + {{generic_payment_details.amount|floatformat:2|intcomma}} CHF +
{% else %}

{% trans "Product" %}:  From 9f86f445695a5efc86048a41f37ccb5c60001b0a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 10:29:33 +0530 Subject: [PATCH 232/471] Add missing divs --- datacenterlight/templates/datacenterlight/order_detail.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 3e2ecf04..078f5efd 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -79,6 +79,8 @@ {% if generic_payment_details %} +

+

{% trans "Product" %}:  {{ generic_payment_details.product_name }} @@ -95,6 +97,7 @@ Yes

{% endif %} +

@@ -154,6 +157,7 @@ {% trans "Your Price in Total" %} {{generic_payment_details.amount|floatformat:2|intcomma}} CHF
+
{% else %}

{% trans "Product" %}:  From fc8c4579fb42197e29da7377b76f4a0b421f38c2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 10:41:51 +0530 Subject: [PATCH 233/471] Show prices to two decimals --- .../templates/datacenterlight/order_detail.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 078f5efd..b8cd7a4b 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -184,7 +184,7 @@

{% trans "Price Before VAT" %} - {{vm.price|intcomma}} CHF + {{vm.price|floatformat:2|intcomma}} CHF

@@ -207,10 +207,10 @@

Price

-

{{vm.price|intcomma}} CHF

+

{{vm.price|floatformat:2|intcomma}} CHF

-

{{vm.price_with_vat|intcomma}} CHF

+

{{vm.price_with_vat|floatformat:2|intcomma}} CHF

{% if vm.discount.amount > 0 %} @@ -219,10 +219,10 @@

{{vm.discount.name}}

-

-{{vm.discount.amount|intcomma}} CHF

+

-{{vm.discount.amount|floatformat:2|intcomma}} CHF

-

-{{vm.discount.amount_with_vat|intcomma}} CHF

+

-{{vm.discount.amount_with_vat|floatformat:2|intcomma}} CHF

{% endif %} @@ -236,10 +236,10 @@

Total

-

{{vm.price_after_discount|intcomma}} CHF

+

{{vm.price_after_discount|floatformat:2|intcomma}} CHF

-

{{vm.price_after_discount_with_vat|intcomma}} CHF

+

{{vm.price_after_discount_with_vat|floatformat:2|intcomma}} CHF

From c0683d9f538769791e3b2fd6623b9ca24a706a61 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 10:57:14 +0530 Subject: [PATCH 234/471] Show prices to two decimal places --- datacenterlight/templatetags/custom_tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index 4cc50caa..0574d29f 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -106,7 +106,7 @@ def get_line_item_from_stripe_invoice(invoice): period = mark_safe("%s — %s" % ( datetime.datetime.fromtimestamp(start_date).strftime('%Y-%m-%d'), datetime.datetime.fromtimestamp(end_date).strftime('%Y-%m-%d'))), - total=invoice.total/100, + total='%.2f' % (invoice.total/100), stripe_invoice_url=invoice.hosted_invoice_url, see_invoice_text=_("See Invoice") )) From e094930d6eec56e523e182e299746601d2e0f046 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 12:39:19 +0530 Subject: [PATCH 235/471] Show product name in invoices list if product is not a VM --- datacenterlight/templatetags/custom_tags.py | 29 +++++++++++++++++++-- hosting/templates/hosting/invoices.html | 2 +- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index 0574d29f..cb0fb6fa 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 from utils.hosting_utils import get_ip_addresses +logger = logging.getLogger(__name__) + register = template.Library() @@ -72,8 +76,10 @@ def get_line_item_from_stripe_invoice(invoice): end_date = 0 is_first = True vm_id = -1 + plan_name = "" for line_data in invoice["lines"]["data"]: if is_first: + plan_name = line_data.plan.name start_date = line_data.period.start end_date = line_data.period.end is_first = False @@ -102,8 +108,9 @@ def get_line_item_from_stripe_invoice(invoice): """.format( vm_id=vm_id if vm_id > 0 else "", - ip_addresses=mark_safe(get_ip_addresses(vm_id)) if vm_id > 0 else "", - period = mark_safe("%s — %s" % ( + ip_addresses=mark_safe(get_ip_addresses(vm_id)) if vm_id > 0 else + mark_safe(plan_name), + period=mark_safe("%s — %s" % ( datetime.datetime.fromtimestamp(start_date).strftime('%Y-%m-%d'), datetime.datetime.fromtimestamp(end_date).strftime('%Y-%m-%d'))), total='%.2f' % (invoice.total/100), @@ -112,3 +119,21 @@ def get_line_item_from_stripe_invoice(invoice): )) else: return "" + + +def get_product_name(plan_name): + product_name = "" + if plan_name and plan_name.startswith("generic-"): + product_id = plan_name[plan_name.index("-") + 1:plan_name.rindex("-")] + try: + product = GenericProduct.objects.get(id=product_id) + product_name = product.product_name + except GenericProduct.DoesNotExist as dne: + logger.error("Generic product id=%s does not exist" % product_id) + product_name = "Unknown" + except GenericProduct.MultipleObjectsReturned as mor: + logger.error("Multiple products with id=%s exist" % product_id) + product_name = "Unknown" + else: + logger.debug("Product name for plan %s does not exist" % plan_name) + return product_name diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index f48802d1..1a97fd1f 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -19,7 +19,7 @@ {% trans "VM ID" %} - {% trans "IP Address" %} + {% trans "IP Address" %}/{% trans "Product" %} {% trans "Period" %} {% trans "Amount" %} From 42a4a77c0278cf2c4336a605f0d9b1715d9ba304 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 12:42:19 +0530 Subject: [PATCH 236/471] Show product name --- datacenterlight/templatetags/custom_tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index cb0fb6fa..a2d9bec6 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -109,7 +109,7 @@ def get_line_item_from_stripe_invoice(invoice): """.format( vm_id=vm_id if vm_id > 0 else "", ip_addresses=mark_safe(get_ip_addresses(vm_id)) if vm_id > 0 else - mark_safe(plan_name), + mark_safe(get_product_name(plan_name)), period=mark_safe("%s — %s" % ( datetime.datetime.fromtimestamp(start_date).strftime('%Y-%m-%d'), datetime.datetime.fromtimestamp(end_date).strftime('%Y-%m-%d'))), From 70a3620598cf06f904c01276470096670b1c069f Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 13:09:27 +0530 Subject: [PATCH 237/471] Fix getting product from plan_name --- datacenterlight/templatetags/custom_tags.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index a2d9bec6..d86ba27d 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -124,13 +124,15 @@ def get_line_item_from_stripe_invoice(invoice): def get_product_name(plan_name): product_name = "" if plan_name and plan_name.startswith("generic-"): - product_id = plan_name[plan_name.index("-") + 1:plan_name.rindex("-")] + first_index_hyphen = plan_name.index("-") + 1 + product_id = plan_name[first_index_hyphen: + (plan_name[first_index_hyphen:].index("-")) + first_index_hyphen] try: product = GenericProduct.objects.get(id=product_id) product_name = product.product_name except GenericProduct.DoesNotExist as dne: logger.error("Generic product id=%s does not exist" % product_id) - product_name = "Unknown" + product_name = plan_name except GenericProduct.MultipleObjectsReturned as mor: logger.error("Multiple products with id=%s exist" % product_id) product_name = "Unknown" From a5d393ad20042770853dcb4c4765dac876b60a4b Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 13:21:01 +0530 Subject: [PATCH 238/471] Right align prices --- datacenterlight/templatetags/custom_tags.py | 2 +- hosting/static/hosting/css/orders.css | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index d86ba27d..0cb18e5b 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -102,7 +102,7 @@ def get_line_item_from_stripe_invoice(invoice): {vm_id} {ip_addresses} {period} - {total} + {total} {see_invoice_text} diff --git a/hosting/static/hosting/css/orders.css b/hosting/static/hosting/css/orders.css index 6819a94b..2deab999 100644 --- a/hosting/static/hosting/css/orders.css +++ b/hosting/static/hosting/css/orders.css @@ -2,4 +2,10 @@ .orders-container .table > tbody > tr > td { vertical-align: middle; +} + +@media screen and (min-width:767px){ + .dcl-text-right { + padding-right: 20px; + } } \ No newline at end of file From f0f8af23674f1be9b0bd353b507a7e33df249a78 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 21:39:51 +0530 Subject: [PATCH 239/471] Change col-sm-8 to col-sm-9 to spread content further on desktop view --- .../datacenterlight/order_detail.html | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index b8cd7a4b..2897e3bf 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -80,7 +80,7 @@ {% if generic_payment_details %}
-
+

{% trans "Product" %}:  {{ generic_payment_details.product_name }} @@ -101,7 +101,7 @@


-
+

{% trans "Price Before VAT" %} {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF @@ -110,7 +110,7 @@


-
+

@@ -137,7 +137,7 @@

-
+

Total

@@ -153,7 +153,7 @@

-
+
{% trans "Your Price in Total" %} {{generic_payment_details.amount|floatformat:2|intcomma}} CHF
@@ -164,7 +164,7 @@ {{ request.session.template.name }}

-
+

{% trans "Cores" %}: {{vm.cpu|floatformat}} @@ -181,7 +181,7 @@


-
+

{% trans "Price Before VAT" %} {{vm.price|floatformat:2|intcomma}} CHF @@ -190,7 +190,7 @@


-
+

@@ -230,7 +230,7 @@

-
+

Total

@@ -246,7 +246,7 @@

-
+
{% trans "Your Price in Total" %} {{vm.total_price|floatformat:2|intcomma}} CHF
From 1630dc195b96f839d249f2b2d64a97786a20e7aa Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 21:52:48 +0530 Subject: [PATCH 240/471] Reduce header font size --- datacenterlight/templates/datacenterlight/order_detail.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 2897e3bf..5060f1cc 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -61,7 +61,7 @@