From 4dd49051b4165fb0fe3b3bbdb63acfc1180ae576 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Sun, 15 Sep 2019 09:38:36 +0530
Subject: [PATCH 001/522] Update Changelog for 2.6.4
---
Changelog | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Changelog b/Changelog
index 1608db2b..06996542 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,5 @@
+2.6.4: 2019-09-15
+ * #7147: [OpenBSD vm] Add a explanatory text for username puffy on OpenBSD (MR!714)
2.6.3: 2019-08-28
* #7032: [hosting] Bugfix: Reentering the same SSH key used before does allow user to proceed further; complains key exists (MR!712)
* #7070: [check_vm/api] Bugfix: Provide oneadmin credentials to check whether a user is the owner of a VM (MR!713)
From 8cd7a6916288c9745092926323363031cc9eaa81 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Tue, 24 Sep 2019 09:44:45 +0530
Subject: [PATCH 002/522] Convert lazy loaded string to str
---
hosting/views.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/hosting/views.py b/hosting/views.py
index 6d023437..a3ae1fa7 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1619,9 +1619,10 @@ class VirtualMachineView(LoginRequiredMixin, View):
else:
sleep(2)
if not response['status']:
- response['text'] = _("VM terminate action timed out. Please "
- "contact support@datacenterlight.ch for "
- "further information.")
+ response['text'] = str(_("VM terminate action timed out. "
+ "Please contact "
+ "support@datacenterlight.ch for "
+ "further information."))
context = {
'vm_name': vm_name,
'base_url': "{0}://{1}".format(
From cc03c11c4aceea008d44fd5536e911521c6f21a9 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Tue, 24 Sep 2019 10:34:04 +0530
Subject: [PATCH 003/522] Improve admin email logging
---
hosting/views.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/hosting/views.py b/hosting/views.py
index a3ae1fa7..bb00978d 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1558,6 +1558,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
# Cancel Stripe subscription
stripe_utils = StripeUtils()
+ hosting_order = None
try:
hosting_order = HostingOrder.objects.get(
vm_id=vm.id
@@ -1643,6 +1644,11 @@ class VirtualMachineView(LoginRequiredMixin, View):
email = BaseEmail(**email_data)
email.send()
admin_email_body.update(response)
+ admin_email_body["customer_email"] = owner.email
+ admin_email_body["VM_ID"] = vm.id
+ admin_email_body["VM_created_at"] = (str(hosting_order.created_at) if
+ hosting_order is not None
+ else "unknown")
admin_msg_sub = "VM and Subscription for VM {} and user: {}".format(
vm.id,
owner.email
From 80c1f8314b1062a57924db149e4ffbdc9395ab33 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Tue, 24 Sep 2019 10:38:11 +0530
Subject: [PATCH 004/522] Update Changelog
---
Changelog | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/Changelog b/Changelog
index 06996542..453df8a8 100644
--- a/Changelog
+++ b/Changelog
@@ -1,5 +1,8 @@
+2.6.5: 2019-09-24
+ * #7169: [hosting] Fix server error while vm terminate takes longer than 30 seconds
+ * #7170: [hosting] Improve admin email body contents for hosting vm terminate error case
2.6.4: 2019-09-15
- * #7147: [OpenBSD vm] Add a explanatory text for username puffy on OpenBSD (MR!714)
+ * #7147: [OpenBSD vm] Add an explanatory text for username puffy on OpenBSD (MR!714)
2.6.3: 2019-08-28
* #7032: [hosting] Bugfix: Reentering the same SSH key used before does allow user to proceed further; complains key exists (MR!712)
* #7070: [check_vm/api] Bugfix: Provide oneadmin credentials to check whether a user is the owner of a VM (MR!713)
From 6d8782415f2dd6a50d480aa7d1fd8949d0c78388 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Tue, 8 Oct 2019 06:33:52 +0530
Subject: [PATCH 005/522] Fix number formatting for price in invoice details
---
hosting/templates/hosting/invoice_detail.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index e84b03ea..d757d476 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -93,10 +93,10 @@
| Product | Period | Qty | Unit Price | Total |
{% for line_item in line_items %}
- | {% if line_item.description|length > 0 %}{{line_item.description}}{% elif line_item.stripe_plan.stripe_plan_name|length > 0 %}{{line_item.stripe_plan.stripe_plan_name}}{% else %}{{line_item.get_item_detail_str|safe}}{% endif %} | {{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }} | {{line_item.quantity}} | {{line_item.unit_amount_in_chf}} | {{line_item.amount_in_chf}} |
+ | {% if line_item.description|length > 0 %}{{line_item.description}}{% elif line_item.stripe_plan.stripe_plan_name|length > 0 %}{{line_item.stripe_plan.stripe_plan_name}}{% else %}{{line_item.get_item_detail_str|safe}}{% endif %} | {{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }} | {{line_item.quantity}} | {{line_item.unit_amount_in_chf}} | {{line_item.amount_in_chf|floatformat:2}} |
{% endfor %}
- | Grand Total | {{total_in_chf}} |
+ | Grand Total | {{total_in_chf|floatformat:2}} |
{% else %}
From 6638d376b85c44879154d1f95f70c8098149d1cd Mon Sep 17 00:00:00 2001
From: PCoder
Date: Sat, 26 Oct 2019 10:32:49 +0530
Subject: [PATCH 006/522] Make HostingBillLineAmount accept negative values
---
hosting/migrations/0056_auto_20191026_0454.py | 20 +++++++++++++++++++
hosting/models.py | 2 +-
2 files changed, 21 insertions(+), 1 deletion(-)
create mode 100644 hosting/migrations/0056_auto_20191026_0454.py
diff --git a/hosting/migrations/0056_auto_20191026_0454.py b/hosting/migrations/0056_auto_20191026_0454.py
new file mode 100644
index 00000000..490964bc
--- /dev/null
+++ b/hosting/migrations/0056_auto_20191026_0454.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2019-10-26 04:54
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('hosting', '0055_auto_20190701_1614'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='hostingbilllineitem',
+ name='amount',
+ field=models.IntegerField(),
+ ),
+ ]
diff --git a/hosting/models.py b/hosting/models.py
index 7b08948e..c9ca5efe 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -466,7 +466,7 @@ class HostingBillLineItem(AssignPermissionsMixin, models.Model):
on_delete=models.CASCADE)
stripe_plan = models.ForeignKey(StripePlan, null=True,
on_delete=models.CASCADE)
- amount = models.PositiveSmallIntegerField()
+ amount = models.IntegerField()
description = models.CharField(max_length=255)
discountable = models.BooleanField()
metadata = models.CharField(max_length=128)
From b06c4d541f329c35dbd265b3ba5e2fa79e4b82e2 Mon Sep 17 00:00:00 2001
From: Mondi Ravi
Date: Mon, 4 Nov 2019 07:16:59 +0100
Subject: [PATCH 007/522] Feature/add userdump
---
.../management/commands/deleteuser.py | 11 --
.../management/commands/dumpuser.py | 137 ++++++++++++++++++
2 files changed, 137 insertions(+), 11 deletions(-)
create mode 100644 datacenterlight/management/commands/dumpuser.py
diff --git a/datacenterlight/management/commands/deleteuser.py b/datacenterlight/management/commands/deleteuser.py
index 1b60e698..1d57aa41 100644
--- a/datacenterlight/management/commands/deleteuser.py
+++ b/datacenterlight/management/commands/deleteuser.py
@@ -98,17 +98,6 @@ class Command(BaseCommand):
logger.error(
"Error while deleting the billing_address")
- # Delete Order Detail
- if order.order_detail is not None:
- logger.debug(
- "Order Detail {} associated with {} deleted"
- "".format(order.order_detail.id, email)
- )
- order.order_detail.delete()
- else:
- logger.error(
- "Error while deleting the order_detail. None")
-
# Delete order
if order is not None:
logger.debug(
diff --git a/datacenterlight/management/commands/dumpuser.py b/datacenterlight/management/commands/dumpuser.py
new file mode 100644
index 00000000..aa86371e
--- /dev/null
+++ b/datacenterlight/management/commands/dumpuser.py
@@ -0,0 +1,137 @@
+import logging
+import sys
+from pprint import pprint
+
+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 data of a customer into a json file'''
+
+ def add_arguments(self, parser):
+ parser.add_argument('customer_email', nargs='+', type=str)
+
+ def handle(self, *args, **options):
+ try:
+ for email in options['customer_email']:
+ logger.debug("Creating dump for the user {}".format(email))
+ try:
+ cus_user = CustomUser.objects.get(email=email)
+ except CustomUser.DoesNotExist as dne:
+ logger.error("CustomUser with email {} does "
+ "not exist".format(email))
+ sys.exit(1)
+
+ hosting_orders = HostingOrder.objects.filter(
+ customer=cus_user.stripecustomer.id
+ )
+
+ vm_ids = []
+ orders_dict = {}
+ for order in hosting_orders:
+ order_dict = {}
+ vm_ids.append(order.vm_id)
+ order_dict["VM_ID"] = order.vm_id
+ order_dict["Order Nr."] = order.id
+ order_dict["Created On"] = str(order.created_on)
+ order_dict["Price"] = order.price
+ order_dict["Payment card details"] = {
+ "last4": order.last4,
+ "brand": order.cc_brand
+ }
+ if order.subscription_id is not None and order.stripe_charge_id is None:
+ order_dict["Order type"] = "Monthly susbcription"
+ else:
+ order_dict["Order type"] = "One time payment"
+
+ # billing address
+ if order.billing_address is not None:
+ order_dict["Billing Address"] = {
+ "Street": order.billing_address.street_address,
+ "City": order.billing_address.city,
+ "Country": order.billing_address.country,
+ "Postal code": order.billing_address.postal_code,
+ "Card holder name": order.billing_address.cardholder_name
+ }
+ else:
+ logger.error(
+ "did not find billing_address")
+
+ # Order Detail
+ if order.order_detail is not None:
+ order_dict["Specifications"] = {
+ "RAM": "{} GB".format(order.order_detail.memory),
+ "Cores": order.order_detail.cores,
+ "Disk space (SSD)": "{} GB".format(
+ order.order_detail.ssd_size)
+ }
+ else:
+ logger.error(
+ "Did not find order_detail. None")
+
+ vm_detail = VMDetail.objects.get(vm_id=order.vm_id)
+ if vm_detail is not None:
+ order_dict["VM Details"] = {
+ "VM_ID": order.vm_id,
+ "IPv4": vm_detail.ipv4,
+ "IPv6": vm_detail.ipv6,
+ "OS": vm_detail.configuration,
+ }
+ order_dict["Terminated on"] = vm_detail.terminated_at
+
+ orders_dict[order.vm_id] = order_dict
+
+
+ # UserCardDetail
+ cards = {}
+ ucds = UserCardDetail.objects.filter(
+ stripe_customer=cus_user.stripecustomer
+ )
+ for ucd in ucds:
+ card = {}
+ if ucd is not None:
+ card["Last 4"] = ucd.last4
+ card["Brand"] = ucd.brand
+ card["Expiry month"] = ucd.exp_month
+ card["Expiry year"] = ucd.exp_year
+ card["Preferred"] = ucd.preferred
+ cards[ucd.id] = card
+ else:
+ logger.error(
+ "Error while deleting the User Card Detail")
+
+ # UserHostingKey
+ keys = {}
+ uhks = UserHostingKey.objects.filter(
+ user=cus_user
+ )
+ for uhk in uhks:
+ key = {
+ "Public key": uhk.public_key,
+ "Name": uhk.name,
+ "Created on": str(uhk.created_at)
+ }
+ if uhk.private_key is not None:
+ key["Private key"] = uhk.private_key
+ keys[uhk.name] = key
+ print("User {} dump is follows:")
+ output_dict = {
+ "User details": {
+ "Name": cus_user.name,
+ "Email": cus_user.email,
+ "Activated": "yes" if cus_user.validated == 1 else "no",
+ "Last login": str(cus_user.last_login)
+ },
+ "Orders": orders_dict,
+ "Payment cards": cards,
+ "SSH Keys": keys
+ }
+ pprint(output_dict)
+ logger.debug("Dumped user {} SUCCESSFULLY.".format(email))
+ except Exception as e:
+ print(" *** Error occurred. Details {}".format(str(e)))
From b35a1a9e9bc7668d66342cf3411cf38ee63c23e7 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Mon, 4 Nov 2019 11:50:57 +0530
Subject: [PATCH 008/522] Update Changelog
---
Changelog | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Changelog b/Changelog
index 453df8a8..a219bbee 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,5 @@
+2.6.6: 2019-11-04
+ * feature: [admin] Add dumpuser management command that dumps a user's data in json (MR!716)
2.6.5: 2019-09-24
* #7169: [hosting] Fix server error while vm terminate takes longer than 30 seconds
* #7170: [hosting] Improve admin email body contents for hosting vm terminate error case
From 72741f21881ea7900c7736db134cb5331debf631 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Mon, 4 Nov 2019 11:59:44 +0530
Subject: [PATCH 009/522] Fix bugs
- Use correct attribute created_at instead of created_on
- Convert yet another date to str (missed earlier)
---
datacenterlight/management/commands/dumpuser.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/datacenterlight/management/commands/dumpuser.py b/datacenterlight/management/commands/dumpuser.py
index aa86371e..2e97e135 100644
--- a/datacenterlight/management/commands/dumpuser.py
+++ b/datacenterlight/management/commands/dumpuser.py
@@ -38,7 +38,7 @@ class Command(BaseCommand):
vm_ids.append(order.vm_id)
order_dict["VM_ID"] = order.vm_id
order_dict["Order Nr."] = order.id
- order_dict["Created On"] = str(order.created_on)
+ order_dict["Created On"] = str(order.created_at)
order_dict["Price"] = order.price
order_dict["Payment card details"] = {
"last4": order.last4,
@@ -82,7 +82,7 @@ class Command(BaseCommand):
"IPv6": vm_detail.ipv6,
"OS": vm_detail.configuration,
}
- order_dict["Terminated on"] = vm_detail.terminated_at
+ order_dict["Terminated on"] = str(vm_detail.terminated_at)
orders_dict[order.vm_id] = order_dict
From c29193f6c82d8f282723cf5c2996d5767f26207a Mon Sep 17 00:00:00 2001
From: PCoder
Date: Mon, 4 Nov 2019 12:05:55 +0530
Subject: [PATCH 010/522] Fix bugs
- fetch_stripe_bills:
- fix wrong assigment of strign to num_invoice_created variable
- return None (do not handle the case) if we don't have an order
---
hosting/management/commands/fetch_stripe_bills.py | 7 ++++++-
hosting/models.py | 5 ++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py
index 20f1cbe0..1e4d1ab3 100644
--- a/hosting/management/commands/fetch_stripe_bills.py
+++ b/hosting/management/commands/fetch_stripe_bills.py
@@ -50,7 +50,12 @@ class Command(BaseCommand):
logger.debug("Invoice %s exists already. Not importing." % invoice['invoice_id'])
except MonthlyHostingBill.DoesNotExist as dne:
logger.debug("Invoice id %s does not exist" % invoice['invoice_id'])
- num_invoice_created += 1 if MonthlyHostingBill.create(invoice) is not None else logger.error("Did not import invoice for %s" % str(invoice))
+
+ if MonthlyHostingBill.create(invoice) is not None:
+ num_invoice_created += 1
+ else:
+ logger.error("Did not import invoice for %s"
+ "" % str(invoice))
self.stdout.write(
self.style.SUCCESS("Number of invoices imported = %s" % num_invoice_created)
)
diff --git a/hosting/models.py b/hosting/models.py
index c9ca5efe..2a9bd28d 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -319,7 +319,10 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
logger.debug("Neither subscription id nor vm_id available")
logger.debug("Can't import invoice")
return None
-
+ if args['order'] is None:
+ logger.error(
+ "Order is None for {}".format(args['invoice_id']))
+ return None
instance = cls.objects.create(
created=datetime.utcfromtimestamp(
args['created']).replace(tzinfo=pytz.utc),
From 6faa8b82e817900d8d8856deccf27d53db54e591 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Mon, 4 Nov 2019 12:10:22 +0530
Subject: [PATCH 011/522] Remove unwanted logger/print statements
---
datacenterlight/management/commands/dumpuser.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/datacenterlight/management/commands/dumpuser.py b/datacenterlight/management/commands/dumpuser.py
index 2e97e135..3f3b7695 100644
--- a/datacenterlight/management/commands/dumpuser.py
+++ b/datacenterlight/management/commands/dumpuser.py
@@ -19,7 +19,6 @@ class Command(BaseCommand):
def handle(self, *args, **options):
try:
for email in options['customer_email']:
- logger.debug("Creating dump for the user {}".format(email))
try:
cus_user = CustomUser.objects.get(email=email)
except CustomUser.DoesNotExist as dne:
@@ -119,7 +118,6 @@ class Command(BaseCommand):
if uhk.private_key is not None:
key["Private key"] = uhk.private_key
keys[uhk.name] = key
- print("User {} dump is follows:")
output_dict = {
"User details": {
"Name": cus_user.name,
@@ -132,6 +130,5 @@ class Command(BaseCommand):
"SSH Keys": keys
}
pprint(output_dict)
- logger.debug("Dumped user {} SUCCESSFULLY.".format(email))
except Exception as e:
print(" *** Error occurred. Details {}".format(str(e)))
From 7aec4dd93818a90bc45647e0201ed4849e1f9726 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Mon, 4 Nov 2019 12:15:52 +0530
Subject: [PATCH 012/522] Convert dict to json and then dump + fix checking
None on FileField
---
datacenterlight/management/commands/dumpuser.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/datacenterlight/management/commands/dumpuser.py b/datacenterlight/management/commands/dumpuser.py
index 3f3b7695..870c73c1 100644
--- a/datacenterlight/management/commands/dumpuser.py
+++ b/datacenterlight/management/commands/dumpuser.py
@@ -1,3 +1,4 @@
+import json
import logging
import sys
from pprint import pprint
@@ -115,7 +116,7 @@ class Command(BaseCommand):
"Name": uhk.name,
"Created on": str(uhk.created_at)
}
- if uhk.private_key is not None:
+ if uhk.private_key:
key["Private key"] = uhk.private_key
keys[uhk.name] = key
output_dict = {
@@ -129,6 +130,6 @@ class Command(BaseCommand):
"Payment cards": cards,
"SSH Keys": keys
}
- pprint(output_dict)
+ pprint(json.dumps(output_dict))
except Exception as e:
print(" *** Error occurred. Details {}".format(str(e)))
From 4174c6226fd05303ffc2ef0666fd4661e7896d01 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Mon, 4 Nov 2019 12:19:25 +0530
Subject: [PATCH 013/522] Remove pprint (does not seem to help)
---
datacenterlight/management/commands/dumpuser.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/datacenterlight/management/commands/dumpuser.py b/datacenterlight/management/commands/dumpuser.py
index 870c73c1..24857dec 100644
--- a/datacenterlight/management/commands/dumpuser.py
+++ b/datacenterlight/management/commands/dumpuser.py
@@ -1,7 +1,6 @@
import json
import logging
import sys
-from pprint import pprint
from django.core.management.base import BaseCommand
from membership.models import CustomUser
@@ -130,6 +129,6 @@ class Command(BaseCommand):
"Payment cards": cards,
"SSH Keys": keys
}
- pprint(json.dumps(output_dict))
+ print(json.dumps(output_dict, indent=4))
except Exception as e:
print(" *** Error occurred. Details {}".format(str(e)))
From 270a03e7c510301f6b4ee99d0895deaffa4eba83 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Mon, 4 Nov 2019 17:09:40 +0530
Subject: [PATCH 014/522] Improve deleteuser
Do not delete order, bill and vm_detail
---
.../management/commands/deleteuser.py | 86 +++----------------
1 file changed, 11 insertions(+), 75 deletions(-)
diff --git a/datacenterlight/management/commands/deleteuser.py b/datacenterlight/management/commands/deleteuser.py
index 1d57aa41..d4ccba29 100644
--- a/datacenterlight/management/commands/deleteuser.py
+++ b/datacenterlight/management/commands/deleteuser.py
@@ -1,14 +1,17 @@
import logging
-import oca
import sys
-import stripe
+import uuid
+import oca
+import stripe
from django.core.management.base import BaseCommand
-from membership.models import CustomUser, DeletedUser
+
from hosting.models import (
- HostingOrder, HostingBill, VMDetail, UserCardDetail, UserHostingKey
+ UserCardDetail, UserHostingKey
)
+from membership.models import CustomUser, DeletedUser
from opennebula_api.models import OpenNebulaManager
+
logger = logging.getLogger(__name__)
@@ -79,75 +82,6 @@ class Command(BaseCommand):
else:
logger.error("Error while deleting the StripeCustomer")
- hosting_orders = HostingOrder.objects.filter(
- customer=stripe_customer.id
- )
-
- vm_ids = []
- for order in hosting_orders:
- vm_ids.append(order.vm_id)
-
- # Delete Billing Address
- if order.billing_address is not None:
- logger.debug(
- "Billing Address {} associated with {} deleted"
- "".format(order.billing_address.id, email)
- )
- order.billing_address.delete()
- else:
- logger.error(
- "Error while deleting the billing_address")
-
- # Delete order
- if order is not None:
- logger.debug(
- "Order {} associated with {} deleted"
- "".format(order.id, email)
- )
- order.delete()
- else:
- logger.error(
- "Error while deleting the Order")
-
- hosting_bills = HostingBill.objects.filter(
- customer=stripe_customer.id
- )
-
- # delete hosting bills
- for bill in hosting_bills:
- if bill.billing_address is not None:
- logger.debug(
- "HostingBills billing address {} associated with {} deleted"
- "".format(bill.billing_address.id, email)
- )
- bill.billing_address.delete()
- else:
- logger.error(
- "Error while deleting the HostingBill's Billing address")
-
- if bill is not None:
- logger.debug(
- "HostingBill {} associated with {} deleted"
- "".format(bill.id, email)
- )
- bill.delete()
- else:
- logger.error(
- "Error while deleting the HostingBill")
-
- # delete VMDetail
- for vm_id in vm_ids:
- vm_detail = VMDetail.objects.get(vm_id=vm_id)
- if vm_detail is not None:
- logger.debug(
- "vm_detail {} associated with {} deleted"
- "".format(vm_detail.id, email)
- )
- vm_detail.delete()
- else:
- logger.error(
- "Error while deleting the vm_detail")
-
# delete UserCardDetail
ucds = UserCardDetail.objects.filter(
stripe_customer=stripe_customer
@@ -179,8 +113,10 @@ class Command(BaseCommand):
user_id = cus_user.id
)
- # delete CustomUser
- cus_user.delete()
+ # reset CustomUser
+ cus_user.email = str(uuid.uuid4())
+ cus_user.validated = 0
+ cus_user.save()
# remove user from OpenNebula
manager = OpenNebulaManager()
From 2d916936d6aba6933be5273ba8c14588d7baed5e Mon Sep 17 00:00:00 2001
From: PCoder
Date: Mon, 4 Nov 2019 17:17:27 +0530
Subject: [PATCH 015/522] Update Changelog
---
Changelog | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Changelog b/Changelog
index a219bbee..7023cae5 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,11 @@
+2.6.7: 2019-11-04
+ * bugfix: [admin] Improve dumpuser: show proper dates + bugfix
+ * bugfix: [admin] Improve fetch_stripe_bills:
+ - fix wrong assigment of string to num_invoice_created
+ variable,
+ - return None (do not handle the case) if we don't have an
+ order
+ * bugfix: [admin] Improve deleteuser: do not delete order, bill and vm_detail
2.6.6: 2019-11-04
* feature: [admin] Add dumpuser management command that dumps a user's data in json (MR!716)
2.6.5: 2019-09-24
From c56d6bd62708b49071fb406270e889930c7564be Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 11:01:49 +0530
Subject: [PATCH 016/522] Add VATRates model
---
hosting/models.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/hosting/models.py b/hosting/models.py
index 2a9bd28d..5f0ec3ef 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -707,3 +707,13 @@ class UserCardDetail(AssignPermissionsMixin, models.Model):
return ucd
except UserCardDetail.DoesNotExist:
return None
+
+
+class VATRates(AssignPermissionsMixin, models.Model):
+ start_date = models.DateField(blank=True, null=True)
+ stop_date = models.DateField(blank=True, null=True)
+ territory_codes = models.TextField(blank=True, default='')
+ currency_code = models.CharField(max_length=10)
+ rate = models.FloatField()
+ rate_type = models.TextField(blank=True, default='')
+ description = models.TextField(blank=True, default='')
\ No newline at end of file
From 7038a36b4df0c2cbed1638df2b825d93f4b386a4 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 11:02:06 +0530
Subject: [PATCH 017/522] Add vat_rates csv
---
vat_rates.csv | 314 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 314 insertions(+)
create mode 100644 vat_rates.csv
diff --git a/vat_rates.csv b/vat_rates.csv
new file mode 100644
index 00000000..b82f252b
--- /dev/null
+++ b/vat_rates.csv
@@ -0,0 +1,314 @@
+start_date,stop_date,territory_codes,currency_code,rate,rate_type,description
+2011-01-04,,AI,XCD,0,standard,Anguilla (British overseas territory) is exempted of VAT.
+1984-01-01,,AT,EUR,0.2,standard,Austria (member state) standard VAT rate.
+1976-01-01,1984-01-01,AT,EUR,0.18,standard,
+1973-01-01,1976-01-01,AT,EUR,0.16,standard,
+1984-01-01,,"AT-6691
+DE-87491",EUR,0.19,standard,Jungholz (Austrian town) special VAT rate.
+1984-01-01,,"AT-6991
+AT-6992
+AT-6993
+DE-87567
+DE-87568
+DE-87569",EUR,0.19,standard,Mittelberg (Austrian town) special VAT rate.
+1996-01-01,,BE,EUR,0.21,standard,Belgium (member state) standard VAT rate.
+1994-01-01,1996-01-01,BE,EUR,0.205,standard,
+1992-04-01,1994-01-01,BE,EUR,0.195,standard,
+1983-01-01,1992-04-01,BE,EUR,0.19,standard,
+1981-07-01,1983-01-01,BE,EUR,0.17,standard,
+1978-07-01,1981-07-01,BE,EUR,0.16,standard,
+1971-07-01,1978-07-01,BE,EUR,0.18,standard,
+1999-01-01,,BG,BGN,0.2,standard,Bulgaria (member state) standard VAT rate.
+1996-07-01,1999-01-01,BG,BGN,0.22,standard,
+1994-04-01,1996-07-01,BG,BGN,0.18,standard,
+2011-01-04,,BM,BMD,0,standard,Bermuda (British overseas territory) is exempted of VAT.
+2014-01-13,,"CY
+GB-BFPO 57
+GB-BFPO 58
+GB-BFPO 59
+UK-BFPO 57
+UK-BFPO 58
+UK-BFPO 59",EUR,0.19,standard,"Cyprus (member state) standard VAT rate.
+Akrotiri and Dhekelia (British overseas territory) is subjected to Cyprus' standard VAT rate."
+2013-01-14,2014-01-13,CY,EUR,0.18,standard,
+2012-03-01,2013-01-14,CY,EUR,0.17,standard,
+2003-01-01,2012-03-01,CY,EUR,0.15,standard,
+2002-07-01,2003-01-01,CY,EUR,0.13,standard,
+2000-07-01,2002-07-01,CY,EUR,0.1,standard,
+1993-10-01,2000-07-01,CY,EUR,0.08,standard,
+1992-07-01,1993-10-01,CY,EUR,0.05,standard,
+2013-01-01,,CZ,CZK,0.21,standard,Czech Republic (member state) standard VAT rate.
+2010-01-01,2013-01-01,CZ,CZK,0.2,standard,
+2004-05-01,2010-01-01,CZ,CZK,0.19,standard,
+1995-01-01,2004-05-01,CZ,CZK,0.22,standard,
+1993-01-01,1995-01-01,CZ,CZK,0.23,standard,
+2007-01-01,,DE,EUR,0.19,standard,Germany (member state) standard VAT rate.
+1998-04-01,2007-01-01,DE,EUR,0.16,standard,
+1993-01-01,1998-04-01,DE,EUR,0.15,standard,
+1983-07-01,1993-01-01,DE,EUR,0.14,standard,
+1979-07-01,1983-07-01,DE,EUR,0.13,standard,
+1978-01-01,1979-07-01,DE,EUR,0.12,standard,
+1968-07-01,1978-01-01,DE,EUR,0.11,standard,
+1968-01-01,1968-07-01,DE,EUR,0.1,standard,
+2007-01-01,,DE-27498,EUR,0,standard,Heligoland (German island) is exempted of VAT.
+2007-01-01,,"DE-78266
+CH-8238",EUR,0,standard,Busingen am Hochrhein (German territory) is exempted of VAT.
+1992-01-01,,DK,DKK,0.25,standard,Denmark (member state) standard VAT rate.
+1980-06-30,1992-01-01,DK,DKK,0.22,standard,
+1978-10-30,1980-06-30,DK,DKK,0.2025,standard,
+1977-10-03,1978-10-30,DK,DKK,0.18,standard,
+1970-06-29,1977-10-03,DK,DKK,0.15,standard,
+1968-04-01,1970-06-29,DK,DKK,0.125,standard,
+1967-07-03,1968-04-01,DK,DKK,0.1,standard,
+2009-07-01,,EE,EUR,0.2,standard,Estonia (member state) standard VAT rate.
+1993-01-01,2009-07-01,EE,EUR,0.18,standard,
+1991-01-01,1993-01-01,EE,EUR,0.1,standard,
+2016-06-01,,"GR
+EL",EUR,0.24,standard,Greece (member state) standard VAT rate.
+2010-07-01,2016-06-01,"GR
+EL",EUR,0.23,standard,
+2010-03-15,2010-07-01,"GR
+EL",EUR,0.21,standard,
+2005-04-01,2010-03-15,"GR
+EL",EUR,0.19,standard,
+1990-04-28,2005-04-01,"GR
+EL",EUR,0.18,standard,
+1988-01-01,1990-04-28,"GR
+EL",EUR,0.16,standard,
+1987-01-01,1988-01-01,"GR
+EL",EUR,0.18,standard,
+2012-09-01,,ES,EUR,0.21,standard,Spain (member state) standard VAT rate.
+2010-07-01,2012-09-01,ES,EUR,0.18,standard,
+1995-01-01,2010-07-01,ES,EUR,0.16,standard,
+1992-08-01,1995-01-01,ES,EUR,0.15,standard,
+1992-01-01,1992-08-01,ES,EUR,0.13,standard,
+1986-01-01,1992-01-01,ES,EUR,0.12,standard,
+2012-09-01,,"ES-CN
+ES-GC
+ES-TF
+IC",EUR,0,standard,Canary Islands (Spanish autonomous community) is exempted of VAT.
+2012-09-01,,"ES-ML
+ES-CE
+EA",EUR,0,standard,Ceuta and Melilla (Spanish autonomous cities) is exempted of VAT.
+2013-01-01,,FI,EUR,0.24,standard,Finland (member state) standard VAT rate.
+2010-07-01,2013-01-01,FI,EUR,0.23,standard,
+1994-06-01,2010-07-01,FI,EUR,0.22,standard,
+2013-01-01,,"FI-01
+AX",EUR,0,standard,Aland Islands (Finish autonomous region) is exempted of VAT.
+2011-01-04,,FK,FKP,0,standard,Falkland Islands (British overseas territory) is exempted of VAT.
+1992-01-01,,FO,DKK,0,standard,Faroe Islands (Danish autonomous country) is exempted of VAT.
+2014-01-01,,"FR
+MC",EUR,0.2,standard,"France (member state) standard VAT rate.
+Monaco (sovereign city-state) is member of the EU VAT area and subjected to France's standard VAT rate."
+2000-04-01,2014-01-01,"FR
+MC",EUR,0.196,standard,
+1995-08-01,2000-04-01,"FR
+MC",EUR,0.206,standard,
+1982-07-01,1995-08-01,"FR
+MC",EUR,0.186,standard,
+1977-01-01,1982-07-01,"FR
+MC",EUR,0.176,standard,
+1973-01-01,1977-01-01,"FR
+MC",EUR,0.2,standard,
+1970-01-01,1973-01-01,"FR
+MC",EUR,0.23,standard,
+1968-12-01,1970-01-01,"FR
+MC",EUR,0.19,standard,
+1968-01-01,1968-12-01,"FR
+MC",EUR,0.1666,standard,
+2014-01-01,,"FR-BL
+BL",EUR,0,standard,Saint Barthelemy (French overseas collectivity) is exempted of VAT.
+2014-01-01,,"FR-GF
+GF",EUR,0,standard,Guiana (French overseas department) is exempted of VAT.
+2014-01-01,,"FR-GP
+GP",EUR,0.085,standard,Guadeloupe (French overseas department) special VAT rate.
+2014-01-01,,"FR-MF
+MF",EUR,0,standard,Saint Martin (French overseas collectivity) is subjected to France's standard VAT rate.
+2014-01-01,,"FR-MQ
+MQ",EUR,0.085,standard,Martinique (French overseas department) special VAT rate.
+2014-01-01,,"FR-NC
+NC",XPF,0,standard,New Caledonia (French special collectivity) is exempted of VAT.
+2014-01-01,,"FR-PF
+PF",XPF,0,standard,French Polynesia (French overseas collectivity) is exempted of VAT.
+2014-01-01,,"FR-PM
+PM",EUR,0,standard,Saint Pierre and Miquelon (French overseas collectivity) is exempted of VAT.
+2014-01-01,,"FR-RE
+RE",EUR,0.085,standard,Reunion (French overseas department) special VAT rate.
+2014-01-01,,"FR-TF
+TF",EUR,0,standard,French Southern and Antarctic Lands (French overseas territory) is exempted of VAT.
+2014-01-01,,"FR-WF
+WF",XPF,0,standard,Wallis and Futuna (French overseas collectivity) is exempted of VAT.
+2014-01-01,,"FR-YT
+YT",EUR,0,standard,Mayotte (French overseas department) is exempted of VAT.
+2011-01-04,,GG,GBP,0,standard,Guernsey (British Crown dependency) is exempted of VAT.
+2011-01-04,,GI,GIP,0,standard,Gibraltar (British overseas territory) is exempted of VAT.
+1992-01-01,,GL,DKK,0,standard,Greenland (Danish autonomous country) is exempted of VAT.
+2010-07-01,2016-06-01,"GR-34007
+EL-34007",EUR,0.16,standard,Skyros (Greek island) special VAT rate.
+2010-07-01,2016-06-01,"GR-37002
+GR-37003
+GR-37005
+EL-37002
+EL-37003
+EL-37005",EUR,0.16,standard,Northern Sporades (Greek islands) special VAT rate.
+2010-07-01,2016-06-01,"GR-64004
+EL-64004",EUR,0.16,standard,Thasos (Greek island) special VAT rate.
+2010-07-01,2016-06-01,"GR-68002
+EL-68002",EUR,0.16,standard,Samothrace (Greek island) special VAT rate.
+2010-07-01,,"GR-69
+EL-69",EUR,0,standard,Mount Athos (Greek self-governed part) is exempted of VAT.
+2010-07-01,2016-06-01,"GR-81
+EL-81",EUR,0.16,standard,Dodecanese (Greek department) special VAT rate.
+2010-07-01,2016-06-01,"GR-82
+EL-82",EUR,0.16,standard,Cyclades (Greek department) special VAT rate.
+2010-07-01,2016-06-01,"GR-83
+EL-83",EUR,0.16,standard,Lesbos (Greek department) special VAT rate.
+2010-07-01,2016-06-01,"GR-84
+EL-84",EUR,0.16,standard,Samos (Greek department) special VAT rate.
+2010-07-01,2016-06-01,"GR-85
+EL-85",EUR,0.16,standard,Chios (Greek department) special VAT rate.
+2011-01-04,,GS,GBP,0,standard,South Georgia and the South Sandwich Islands (British overseas territory) is exempted of VAT.
+2012-03-01,,HR,HRK,0.25,standard,Croatia (member state) standard VAT rate.
+2009-08-01,2012-03-01,HR,HRK,0.23,standard,
+1998-08-01,2009-08-01,HR,HRK,0.22,standard,
+2012-01-01,,HU,HUF,0.27,standard,Hungary (member state) standard VAT rate.
+2009-07-01,2012-01-01,HU,HUF,0.25,standard,
+2006-01-01,2009-07-01,HU,HUF,0.2,standard,
+1988-01-01,2006-01-01,HU,HUF,0.25,standard,
+2012-01-01,,IE,EUR,0.23,standard,Republic of Ireland (member state) standard VAT rate.
+2010-01-01,2012-01-01,IE,EUR,0.21,standard,
+2008-12-01,2010-01-01,IE,EUR,0.215,standard,
+2002-03-01,2008-12-01,IE,EUR,0.21,standard,
+2001-01-01,2002-03-01,IE,EUR,0.2,standard,
+1991-03-01,2001-01-01,IE,EUR,0.21,standard,
+1990-03-01,1991-03-01,IE,EUR,0.23,standard,
+1986-03-01,1990-03-01,IE,EUR,0.25,standard,
+1983-05-01,1986-03-01,IE,EUR,0.23,standard,
+1983-03-01,1983-05-01,IE,EUR,0.35,standard,
+1982-05-01,1983-03-01,IE,EUR,0.3,standard,
+1980-05-01,1982-05-01,IE,EUR,0.25,standard,
+1976-03-01,1980-05-01,IE,EUR,0.2,standard,
+1973-09-03,1976-03-01,IE,EUR,0.195,standard,
+1972-11-01,1973-09-03,IE,EUR,0.1637,standard,
+2011-01-04,,IO,GBP,0,standard,British Indian Ocean Territory (British overseas territory) is exempted of VAT.
+2013-10-01,,IT,EUR,0.22,standard,Italy (member state) standard VAT rate.
+2011-09-17,2013-10-01,IT,EUR,0.21,standard,
+1997-10-01,2011-09-17,IT,EUR,0.2,standard,
+1988-08-01,1997-10-01,IT,EUR,0.19,standard,
+1982-08-05,1988-08-01,IT,EUR,0.18,standard,
+1981-01-01,1982-08-05,IT,EUR,0.15,standard,
+1980-11-01,1981-01-01,IT,EUR,0.14,standard,
+1980-07-03,1980-11-01,IT,EUR,0.15,standard,
+1977-02-08,1980-07-03,IT,EUR,0.14,standard,
+1973-01-01,1977-02-08,IT,EUR,0.12,standard,
+2013-10-01,,"IT-22060
+CH-6911",CHF,0,standard,Campione (Italian town) is exempted of VAT.
+2013-10-01,,IT-23030,EUR,0,standard,Livigno (Italian town) is exempted of VAT.
+2011-01-04,,JE,GBP,0,standard,Jersey (British Crown dependency) is exempted of VAT.
+2011-01-04,,KY,KYD,0,standard,Cayman Islands (British overseas territory) is exempted of VAT.
+2009-09-01,,LT,EUR,0.21,standard,Lithuania (member state) standard VAT rate.
+2009-01-01,2009-09-01,LT,EUR,0.19,standard,
+1994-05-01,2009-01-01,LT,EUR,0.18,standard,
+2015-01-01,,LU,EUR,0.17,standard,Luxembourg (member state) standard VAT rate.
+1992-01-01,2015-01-01,LU,EUR,0.15,standard,
+1983-07-01,1992-01-01,LU,EUR,0.12,standard,
+1971-01-01,1983-07-01,LU,EUR,0.1,standard,
+1970-01-01,1971-01-01,LU,EUR,0.8,standard,
+2012-07-01,,LV,EUR,0.21,standard,Latvia (member state) standard VAT rate.
+2011-01-01,2012-07-01,LV,EUR,0.22,standard,
+2009-01-01,2011-01-01,LV,EUR,0.21,standard,
+1995-05-01,2009-01-01,LV,EUR,0.18,standard,
+2011-01-04,,MS,XCD,0,standard,Montserrat (British overseas territory) is exempted of VAT.
+2004-01-01,,MT,EUR,0.18,standard,Malta (member state) standard VAT rate.
+1995-01-01,2004-01-01,MT,EUR,0.15,standard,
+2012-10-01,,NL,EUR,0.21,standard,Netherlands (member state) standard VAT rate.
+2001-01-01,2012-10-01,NL,EUR,0.19,standard,
+1992-10-01,2001-01-01,NL,EUR,0.175,standard,
+1989-01-01,1992-10-01,NL,EUR,0.185,standard,
+1986-10-01,1989-01-01,NL,EUR,0.2,standard,
+1984-01-01,1986-10-01,NL,EUR,0.19,standard,
+1976-01-01,1984-01-01,NL,EUR,0.18,standard,
+1973-01-01,1976-01-01,NL,EUR,0.16,standard,
+1971-01-01,1973-01-01,NL,EUR,0.14,standard,
+1969-01-01,1971-01-01,NL,EUR,0.12,standard,
+2012-10-01,,"NL-AW
+AW",AWG,0,standard,Aruba (Dutch country) are exempted of VAT.
+2012-10-01,,"NL-CW
+NL-SX
+CW
+SX",ANG,0,standard,Curacao and Sint Maarten (Dutch countries) are exempted of VAT.
+2012-10-01,,"NL-BQ1
+NL-BQ2
+NL-BQ3
+BQ
+BQ-BO
+BQ-SA
+BQ-SE",USD,0,standard,"Bonaire, Saba and Sint Eustatius (Dutch special municipalities) are exempted of VAT."
+2011-01-01,,PL,PLN,0.23,standard,Poland (member state) standard VAT rate.
+1993-01-08,2011-01-01,PL,PLN,0.22,standard,
+2011-01-04,,PN,NZD,0,standard,Pitcairn Islands (British overseas territory) is exempted of VAT.
+2011-01-01,,PT,EUR,0.23,standard,Portugal (member state) standard VAT rate.
+2010-07-01,2011-01-01,PT,EUR,0.21,standard,
+2008-07-01,2010-07-01,PT,EUR,0.2,standard,
+2005-07-01,2008-07-01,PT,EUR,0.21,standard,
+2002-06-05,2005-07-01,PT,EUR,0.19,standard,
+1995-01-01,2002-06-05,PT,EUR,0.17,standard,
+1992-03-24,1995-01-01,PT,EUR,0.16,standard,
+1988-02-01,1992-03-24,PT,EUR,0.17,standard,
+1986-01-01,1988-02-01,PT,EUR,0.16,standard,
+2011-01-01,,PT-20,EUR,0.18,standard,Azores (Portuguese autonomous region) special VAT rate.
+2011-01-01,,PT-30,EUR,0.22,standard,Madeira (Portuguese autonomous region) special VAT rate.
+2017-01-01,,RO,RON,0.19,standard,Romania (member state) standard VAT rate.
+2016-01-01,2017-01-01,RO,RON,0.2,standard,Romania (member state) standard VAT rate.
+2010-07-01,2016-01-01,RO,RON,0.24,standard,
+2000-01-01,2010-07-01,RO,RON,0.19,standard,
+1998-02-01,2000-01-01,RO,RON,0.22,standard,
+1993-07-01,1998-02-01,RO,RON,0.18,standard,
+1990-07-01,,SE,SEK,0.25,standard,Sweden (member state) standard VAT rate.
+1983-01-01,1990-07-01,SE,SEK,0.2346,standard,
+1981-11-16,1983-01-01,SE,SEK,0.2151,standard,
+1980-09-08,1981-11-16,SE,SEK,0.2346,standard,
+1977-06-01,1980-09-08,SE,SEK,0.2063,standard,
+1971-01-01,1977-06-01,SE,SEK,0.1765,standard,
+1969-01-01,1971-01-01,SE,SEK,0.1111,standard,
+2011-01-04,,"AC
+SH
+SH-AC
+SH-HL",SHP,0,standard,Ascension and Saint Helena (British overseas territory) is exempted of VAT.
+2011-01-04,,"TA
+SH-TA",GBP,0,standard,Tristan da Cunha (British oversea territory) is exempted of VAT.
+2013-07-01,,SI,EUR,0.22,standard,Slovenia (member state) standard VAT rate.
+2002-01-01,2013-07-01,SI,EUR,0.2,standard,
+1999-07-01,2002-01-01,SI,EUR,0.19,standard,
+2011-01-01,,SK,EUR,0.2,standard,Slovakia (member state) standard VAT rate.
+2004-01-01,2011-01-01,SK,EUR,0.19,standard,
+2003-01-01,2004-01-01,SK,EUR,0.2,standard,
+1996-01-01,2003-01-01,SK,EUR,0.23,standard,
+1993-08-01,1996-01-01,SK,EUR,0.25,standard,
+1993-01-01,1993-08-01,SK,EUR,0.23,standard,
+2011-01-04,,TC,USD,0,standard,Turks and Caicos Islands (British overseas territory) is exempted of VAT.
+2011-01-04,,"GB
+UK
+IM",GBP,0.2,standard,"United Kingdom (member state) standard VAT rate.
+Isle of Man (British self-governing dependency) is member of the EU VAT area and subjected to UK's standard VAT rate."
+2010-01-01,2011-01-04,"GB
+UK
+IM",GBP,0.175,standard,
+2008-12-01,2010-01-01,"GB
+UK
+IM",GBP,0.15,standard,
+1991-04-01,2008-12-01,"GB
+UK
+IM",GBP,0.175,standard,
+1979-06-18,1991-04-01,"GB
+UK
+IM",GBP,0.15,standard,
+1974-07-29,1979-06-18,"GB
+UK
+IM",GBP,0.08,standard,
+1973-04-01,1974-07-29,"GB
+UK
+IM",GBP,0.1,standard,
+2011-01-04,,VG,USD,0,standard,British Virgin Islands (British overseas territory) is exempted of VAT.
+2014-01-01,,CP,EUR,0,standard,Clipperton Island (French overseas possession) is exempted of VAT.
+
From b3dd57f1899a7ba3182d1901f9e045f2abf32df8 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 11:02:45 +0530
Subject: [PATCH 018/522] Add vatrates migration
---
hosting/migrations/0057_vatrates.py | 30 +++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
create mode 100644 hosting/migrations/0057_vatrates.py
diff --git a/hosting/migrations/0057_vatrates.py b/hosting/migrations/0057_vatrates.py
new file mode 100644
index 00000000..494974c6
--- /dev/null
+++ b/hosting/migrations/0057_vatrates.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2019-11-15 05:16
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import utils.mixins
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('hosting', '0056_auto_20191026_0454'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='VATRates',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('start_date', models.DateField(blank=True, null=True)),
+ ('stop_date', models.DateField(blank=True, null=True)),
+ ('territory_codes', models.TextField(blank=True, default='')),
+ ('currency_code', models.CharField(max_length=10)),
+ ('rate', models.FloatField()),
+ ('rate_type', models.TextField(blank=True, default='')),
+ ('description', models.TextField(blank=True, default='')),
+ ],
+ bases=(utils.mixins.AssignPermissionsMixin, models.Model),
+ ),
+ ]
From 7040d908dde68a45159036c24e673782bb5a3737 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 11:03:09 +0530
Subject: [PATCH 019/522] Add import_vat_rates management command
---
.../management/commands/import_vat_rates.py | 44 +++++++++++++++++++
1 file changed, 44 insertions(+)
create mode 100644 hosting/management/commands/import_vat_rates.py
diff --git a/hosting/management/commands/import_vat_rates.py b/hosting/management/commands/import_vat_rates.py
new file mode 100644
index 00000000..f779133d
--- /dev/null
+++ b/hosting/management/commands/import_vat_rates.py
@@ -0,0 +1,44 @@
+from django.core.management.base import BaseCommand
+import csv
+from hosting.models import VATRates
+
+
+class Command(BaseCommand):
+ help = '''Imports VAT Rates. Assume vat rates of format https://github.com/kdeldycke/vat-rates/blob/master/vat_rates.csv'''
+
+ def add_arguments(self, parser):
+ parser.add_argument('csv_file', nargs='+', type=str)
+
+ def handle(self, *args, **options):
+ try:
+ for c_file in options['csv_file']:
+ print("c_file = %s" % c_file)
+ with open(c_file, mode='r') as csv_file:
+ csv_reader = csv.DictReader(csv_file)
+ line_count = 0
+ for row in csv_reader:
+ if line_count == 0:
+ line_count += 1
+ obj, created = VATRates.objects.get_or_create(
+ start_date=row["start_date"],
+ stop_date=row["stop_date"] if row["stop_date"] is not "" else None,
+ territory_codes=row["territory_codes"],
+ currency_code=row["currency_code"],
+ rate=row["rate"],
+ rate_type=row["rate_type"],
+ description=row["description"]
+ )
+ if created:
+ self.stdout.write(self.style.SUCCESS(
+ '%s. %s - %s - %s - %s' % (
+ line_count,
+ obj.start_date,
+ obj.stop_date,
+ obj.territory_codes,
+ obj.rate
+ )
+ ))
+ line_count+=1
+
+ except Exception as e:
+ print(" *** Error occurred. Details {}".format(str(e)))
From e0b2a0b6e226dec773834ac6b1f12e88171e1508 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 11:25:50 +0530
Subject: [PATCH 020/522] Add CH VAT rate
---
vat_rates.csv | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/vat_rates.csv b/vat_rates.csv
index b82f252b..e6aa31af 100644
--- a/vat_rates.csv
+++ b/vat_rates.csv
@@ -311,4 +311,4 @@ UK
IM",GBP,0.1,standard,
2011-01-04,,VG,USD,0,standard,British Virgin Islands (British overseas territory) is exempted of VAT.
2014-01-01,,CP,EUR,0,standard,Clipperton Island (French overseas possession) is exempted of VAT.
-
+2019-11-15,,CH,CHF,0.077,standard,Switzerland standard VAT (added manually)
From 44a20a5029af1836896daa14a7e5fd554bc4f108 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 11:58:15 +0530
Subject: [PATCH 021/522] Apply country specific VAT rates for Generic Products
---
datacenterlight/views.py | 9 ++++++---
hosting/models.py | 5 +++--
utils/hosting_utils.py | 14 +++++++++++++-
3 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/datacenterlight/views.py b/datacenterlight/views.py
index ae649623..e13a31a6 100644
--- a/datacenterlight/views.py
+++ b/datacenterlight/views.py
@@ -26,7 +26,9 @@ from utils.forms import (
BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm,
BillingAddress
)
-from utils.hosting_utils import get_vm_price_with_vat, get_all_public_keys
+from utils.hosting_utils import (
+ get_vm_price_with_vat, get_all_public_keys, get_vat_rate_for_country
+)
from utils.stripe_utils import StripeUtils
from utils.tasks import send_plain_email_task
from .cms_models import DCLCalculatorPluginModel
@@ -414,8 +416,9 @@ class PaymentOrderView(FormView):
)
gp_details = {
"product_name": product.product_name,
- "amount": generic_payment_form.cleaned_data.get(
- 'amount'
+ "amount": product.get_actual_price(
+ explicit_vat=get_vat_rate_for_country(
+ address_form["country"])
),
"recurring": generic_payment_form.cleaned_data.get(
'recurring'
diff --git a/hosting/models.py b/hosting/models.py
index 5f0ec3ef..00c89e11 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -82,9 +82,10 @@ class GenericProduct(AssignPermissionsMixin, models.Model):
def __str__(self):
return self.product_name
- def get_actual_price(self):
+ def get_actual_price(self, vat_rate=None):
+ VAT = vat_rate if vat_rate is not None else self.product_vat
return round(
- self.product_price + (self.product_price * self.product_vat), 2
+ self.product_price + (self.product_price * VAT), 2
)
diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py
index b3c47e6e..9492e1de 100644
--- a/utils/hosting_utils.py
+++ b/utils/hosting_utils.py
@@ -5,7 +5,7 @@ import subprocess
from oca.pool import WrongIdError
from datacenterlight.models import VMPricing
-from hosting.models import UserHostingKey, VMDetail
+from hosting.models import UserHostingKey, VMDetail, VATRates
from opennebula_api.serializers import VirtualMachineSerializer
logger = logging.getLogger(__name__)
@@ -150,6 +150,18 @@ def ping_ok(host_ipv6):
return True
+def get_vat_rate_for_country(country):
+ vat_rate = VATRates.objects.get(
+ territory_codes=country, start_date__isnull=False, stop_date=None
+ )
+ if vat_rate:
+ logger.debug("VAT rate for %s is %s" % (country, vat_rate.rate))
+ return vat_rate.rate
+ else:
+ logger.debug("Did not find VAT rate for %s, returning 0" % country)
+ return 0
+
+
class HostingUtils:
@staticmethod
def clear_items_from_list(from_list, items_list):
From 76c2b9d16caa47f55e8b9e93e73160c317e1466f Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 12:23:44 +0530
Subject: [PATCH 022/522] Fix bug: change arg name
---
datacenterlight/views.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/datacenterlight/views.py b/datacenterlight/views.py
index e13a31a6..b2e04bf6 100644
--- a/datacenterlight/views.py
+++ b/datacenterlight/views.py
@@ -417,8 +417,8 @@ class PaymentOrderView(FormView):
gp_details = {
"product_name": product.product_name,
"amount": product.get_actual_price(
- explicit_vat=get_vat_rate_for_country(
- address_form["country"])
+ vat_rate=get_vat_rate_for_country(
+ address_form.cleaned_data["country"])
),
"recurring": generic_payment_form.cleaned_data.get(
'recurring'
From 582e95218700074f82706ec864825c1fe11226ef Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 12:24:24 +0530
Subject: [PATCH 023/522] Convert VAT rate to decimal to be consistent
---
hosting/models.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/hosting/models.py b/hosting/models.py
index 00c89e11..3fbf3f66 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -1,3 +1,4 @@
+import decimal
import json
import logging
import os
@@ -83,7 +84,7 @@ class GenericProduct(AssignPermissionsMixin, models.Model):
return self.product_name
def get_actual_price(self, vat_rate=None):
- VAT = vat_rate if vat_rate is not None else self.product_vat
+ VAT = decimal.Decimal(vat_rate) if vat_rate is not None else self.product_vat
return round(
self.product_price + (self.product_price * VAT), 2
)
From d399fe6e7915eaa2305e397572716afb1937a7d5 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 12:24:39 +0530
Subject: [PATCH 024/522] Handle DoesNotExist better
---
utils/hosting_utils.py | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py
index 9492e1de..d3492a64 100644
--- a/utils/hosting_utils.py
+++ b/utils/hosting_utils.py
@@ -151,17 +151,20 @@ def ping_ok(host_ipv6):
def get_vat_rate_for_country(country):
- vat_rate = VATRates.objects.get(
- territory_codes=country, start_date__isnull=False, stop_date=None
- )
- if vat_rate:
+ vat_rate = None
+ try:
+ vat_rate = VATRates.objects.get(
+ territory_codes=country, start_date__isnull=False, stop_date=None
+ )
logger.debug("VAT rate for %s is %s" % (country, vat_rate.rate))
return vat_rate.rate
- else:
+ except VATRates.DoesNotExist as dne:
+ logger.debug(str(dne))
logger.debug("Did not find VAT rate for %s, returning 0" % country)
return 0
+
class HostingUtils:
@staticmethod
def clear_items_from_list(from_list, items_list):
From 940eaf3a07bf2e587ce5b9d4f68804d7df3354b8 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 12:39:03 +0530
Subject: [PATCH 025/522] Process prices as floats
---
hosting/models.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hosting/models.py b/hosting/models.py
index 3fbf3f66..b03b833b 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -84,9 +84,9 @@ class GenericProduct(AssignPermissionsMixin, models.Model):
return self.product_name
def get_actual_price(self, vat_rate=None):
- VAT = decimal.Decimal(vat_rate) if vat_rate is not None else self.product_vat
+ VAT = vat_rate if vat_rate is not None else self.product_vat
return round(
- self.product_price + (self.product_price * VAT), 2
+ float(self.product_price) + float(self.product_price * VAT), 2
)
From efe411933f1f6fa1af103ca8a9249c1a29760482 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 12:41:27 +0530
Subject: [PATCH 026/522] Missing float conversions
---
hosting/models.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hosting/models.py b/hosting/models.py
index b03b833b..34360f4d 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -86,7 +86,7 @@ class GenericProduct(AssignPermissionsMixin, models.Model):
def get_actual_price(self, vat_rate=None):
VAT = vat_rate if vat_rate is not None else self.product_vat
return round(
- float(self.product_price) + float(self.product_price * VAT), 2
+ float(self.product_price) + float(self.product_price) * float(VAT), 2
)
From 3599f0bff46ad6d37ac407f01cc0ea38f051d09e Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 13:11:11 +0530
Subject: [PATCH 027/522] Show VAT elegantly
---
.../templates/datacenterlight/order_detail.html | 15 +++++++++++++++
datacenterlight/views.py | 10 ++++++++++
2 files changed, 25 insertions(+)
diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html
index 8a444bef..2aae3391 100644
--- a/datacenterlight/templates/datacenterlight/order_detail.html
+++ b/datacenterlight/templates/datacenterlight/order_detail.html
@@ -55,10 +55,25 @@
+ {% 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" %}:
diff --git a/datacenterlight/views.py b/datacenterlight/views.py
index b2e04bf6..6f7da18e 100644
--- a/datacenterlight/views.py
+++ b/datacenterlight/views.py
@@ -414,8 +414,18 @@ class PaymentOrderView(FormView):
product = generic_payment_form.cleaned_data.get(
'product_name'
)
+ user_country_vat_rate = get_vat_rate_for_country(
+ address_form.cleaned_data["country"]
+ )
gp_details = {
"product_name": product.product_name,
+ "vat_rate": user_country_vat_rate * 100,
+ "vat_amount": round(
+ float(product.product_price) *
+ user_country_vat_rate, 2),
+ "vat_country": address_form.cleaned_data["country"],
+ "amount_before_vat": round(
+ float(product.product_price), 2),
"amount": product.get_actual_price(
vat_rate=get_vat_rate_for_country(
address_form.cleaned_data["country"])
From f5372ecd1e622a41ac1701783a8a7804c06a9dfe Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 13:20:14 +0530
Subject: [PATCH 028/522] Fix bug: use country startswith instead of exact
matching
Countries like FR are represented as FR
MC
---
utils/hosting_utils.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py
index d3492a64..75e6c3de 100644
--- a/utils/hosting_utils.py
+++ b/utils/hosting_utils.py
@@ -154,7 +154,7 @@ def get_vat_rate_for_country(country):
vat_rate = None
try:
vat_rate = VATRates.objects.get(
- territory_codes=country, start_date__isnull=False, stop_date=None
+ territory_codes__startswith=country, start_date__isnull=False, stop_date=None
)
logger.debug("VAT rate for %s is %s" % (country, vat_rate.rate))
return vat_rate.rate
@@ -164,7 +164,6 @@ def get_vat_rate_for_country(country):
return 0
-
class HostingUtils:
@staticmethod
def clear_items_from_list(from_list, items_list):
From 069556d9b6053c2d67cf3466a8b18f779aa2049c Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 13:31:21 +0530
Subject: [PATCH 029/522] Add monaco vat rate
---
vat_rates.csv | 1 +
1 file changed, 1 insertion(+)
diff --git a/vat_rates.csv b/vat_rates.csv
index e6aa31af..2a11ff81 100644
--- a/vat_rates.csv
+++ b/vat_rates.csv
@@ -312,3 +312,4 @@ IM",GBP,0.1,standard,
2011-01-04,,VG,USD,0,standard,British Virgin Islands (British overseas territory) is exempted of VAT.
2014-01-01,,CP,EUR,0,standard,Clipperton Island (French overseas possession) is exempted of VAT.
2019-11-15,,CH,CHF,0.077,standard,Switzerland standard VAT (added manually)
+2019-11-15,,MC,EUR,0.196,standard,Monaco standard VAT (added manually)
From 595409399928a9a04aa04b4cb88bad31a35f6811 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 13:32:04 +0530
Subject: [PATCH 030/522] Add France VAT rate
---
vat_rates.csv | 1 +
1 file changed, 1 insertion(+)
diff --git a/vat_rates.csv b/vat_rates.csv
index 2a11ff81..5ce73b1b 100644
--- a/vat_rates.csv
+++ b/vat_rates.csv
@@ -313,3 +313,4 @@ IM",GBP,0.1,standard,
2014-01-01,,CP,EUR,0,standard,Clipperton Island (French overseas possession) is exempted of VAT.
2019-11-15,,CH,CHF,0.077,standard,Switzerland standard VAT (added manually)
2019-11-15,,MC,EUR,0.196,standard,Monaco standard VAT (added manually)
+2019-11-15,,FR,EUR,0.2,standard,France standard VAT (added manually)
From f6feb8870866aef5cc7ec3c7727933e49241127c Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 13:32:49 +0530
Subject: [PATCH 031/522] Revert back starts with logic
---
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 75e6c3de..9c0243e4 100644
--- a/utils/hosting_utils.py
+++ b/utils/hosting_utils.py
@@ -154,7 +154,7 @@ def get_vat_rate_for_country(country):
vat_rate = None
try:
vat_rate = VATRates.objects.get(
- territory_codes__startswith=country, start_date__isnull=False, stop_date=None
+ territory_codes=country, start_date__isnull=False, stop_date=None
)
logger.debug("VAT rate for %s is %s" % (country, vat_rate.rate))
return vat_rate.rate
From 89418ca0089cb5bd68acdb7e4d20e143688cb1e5 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 13:47:24 +0530
Subject: [PATCH 032/522] Add Greece VAT rate
---
vat_rates.csv | 1 +
1 file changed, 1 insertion(+)
diff --git a/vat_rates.csv b/vat_rates.csv
index 5ce73b1b..8d7832c0 100644
--- a/vat_rates.csv
+++ b/vat_rates.csv
@@ -314,3 +314,4 @@ IM",GBP,0.1,standard,
2019-11-15,,CH,CHF,0.077,standard,Switzerland standard VAT (added manually)
2019-11-15,,MC,EUR,0.196,standard,Monaco standard VAT (added manually)
2019-11-15,,FR,EUR,0.2,standard,France standard VAT (added manually)
+2019-11-15,,GR,EUR,0.24,standard,Greece standard VAT (added manually)
From 871cccc2ae6b004df8b7b78a185de7ffb43aa4bc Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 16:55:39 +0530
Subject: [PATCH 033/522] Add UK VAT manually
---
vat_rates.csv | 1 +
1 file changed, 1 insertion(+)
diff --git a/vat_rates.csv b/vat_rates.csv
index 8d7832c0..4a3ec440 100644
--- a/vat_rates.csv
+++ b/vat_rates.csv
@@ -315,3 +315,4 @@ IM",GBP,0.1,standard,
2019-11-15,,MC,EUR,0.196,standard,Monaco standard VAT (added manually)
2019-11-15,,FR,EUR,0.2,standard,France standard VAT (added manually)
2019-11-15,,GR,EUR,0.24,standard,Greece standard VAT (added manually)
+2019-11-15,,GB,EUR,0.2,standard,UK standard VAT (added manually)
From a33a344b4095a1dd240e75cf1a1b631d267ffbbb Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 17:08:05 +0530
Subject: [PATCH 034/522] Update Changelog for 2.6.8
---
Changelog | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/Changelog b/Changelog
index 7023cae5..8709d151 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,10 @@
+2.6.8: 2019-11-15
+ * feature: [EU VAT] Add EU VAT feature for generic products (MR!717)
+ Notes for deployment:
+ - do a db migrate a to create VATRates table
+ ./manage.py migrate hosting
+ - load vat_rates.csv
+ ./manage.py import_vat_rates vat_rates.csv
2.6.7: 2019-11-04
* bugfix: [admin] Improve dumpuser: show proper dates + bugfix
* bugfix: [admin] Improve fetch_stripe_bills:
From f0b604c6dcf7fa70e3115c35cd397a3e2510426c Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 19:40:53 +0530
Subject: [PATCH 035/522] Update Generic product model to include
product_subscription_interval
---
hosting/models.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/hosting/models.py b/hosting/models.py
index 34360f4d..6050339a 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -79,6 +79,9 @@ class GenericProduct(AssignPermissionsMixin, models.Model):
product_price = models.DecimalField(max_digits=6, decimal_places=2)
product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0)
product_is_subscription = models.BooleanField(default=True)
+ product_subscription_interval = models.CharField(
+ max_length=10, default="month",
+ help_text="Choose between `year` and `month`")
def __str__(self):
return self.product_name
From 3bf2654b50e068bfbf1386077ecdc4346705185d Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 19:45:35 +0530
Subject: [PATCH 036/522] Update ProductPaymentForm for yearly subscription
---
hosting/forms.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/hosting/forms.py b/hosting/forms.py
index d98d258f..947cee44 100644
--- a/hosting/forms.py
+++ b/hosting/forms.py
@@ -109,9 +109,14 @@ class ProductPaymentForm(GenericPaymentForm):
)
)
if self.product.product_is_subscription:
+ payment_type = "month"
+ if self.product.product_subscription_interval == "month":
+ payment_type = _('Monthly subscription')
+ elif self.product.product_subscription_interval == "year":
+ payment_type = _('Yearly subscription')
self.fields['amount'].label = "{amt} ({payment_type})".format(
amt=_('Amount in CHF'),
- payment_type=_('Monthly subscription')
+ payment_type=payment_type
)
else:
self.fields['amount'].label = "{amt} ({payment_type})".format(
From e493a9f3d10cff5bcf064e1f3b5cde763a96f6f7 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 19:47:11 +0530
Subject: [PATCH 037/522] Allow creating yearly/monthly Stripe plans
---
datacenterlight/views.py | 13 +++++++++++--
utils/stripe_utils.py | 10 ++++++++--
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/datacenterlight/views.py b/datacenterlight/views.py
index 6f7da18e..af6627ec 100644
--- a/datacenterlight/views.py
+++ b/datacenterlight/views.py
@@ -437,7 +437,9 @@ class PaymentOrderView(FormView):
'description'
),
"product_id": product.id,
- "product_slug": product.product_slug
+ "product_slug": product.product_slug,
+ "recurring_interval":
+ product.product_subscription_interval
}
request.session["generic_payment_details"] = (
gp_details
@@ -756,6 +758,7 @@ class OrderConfirmationView(DetailView, FormView):
if ('generic_payment_type' not in request.session or
(request.session['generic_payment_details']['recurring'])):
+ recurring_interval = 'month'
if 'generic_payment_details' in request.session:
amount_to_be_charged = (
round(
@@ -768,6 +771,10 @@ class OrderConfirmationView(DetailView, FormView):
amount_to_be_charged
)
stripe_plan_id = plan_name
+ recurring_interval = request.session['generic_payment_details']['recurring_interval']
+ if recurring_interval == "year":
+ plan_name = "{}-yearly".format(plan_name)
+ stripe_plan_id = plan_name
else:
template = request.session.get('template')
specs = request.session.get('specs')
@@ -794,7 +801,9 @@ class OrderConfirmationView(DetailView, FormView):
stripe_plan = stripe_utils.get_or_create_stripe_plan(
amount=amount_to_be_charged,
name=plan_name,
- stripe_plan_id=stripe_plan_id)
+ stripe_plan_id=stripe_plan_id,
+ interval=recurring_interval
+ )
subscription_result = stripe_utils.subscribe_customer_to_plan(
stripe_api_cus_id,
[{"plan": stripe_plan.get(
diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py
index 4334d6cf..e2bdb983 100644
--- a/utils/stripe_utils.py
+++ b/utils/stripe_utils.py
@@ -226,7 +226,8 @@ class StripeUtils(object):
return charge
@handleStripeError
- def get_or_create_stripe_plan(self, amount, name, stripe_plan_id):
+ def get_or_create_stripe_plan(self, amount, name, stripe_plan_id,
+ interval=""):
"""
This function checks if a StripePlan with the given
stripe_plan_id already exists. If it exists then the function
@@ -238,6 +239,10 @@ class StripeUtils(object):
:param stripe_plan_id: The id of the Stripe plan to be
created. Use get_stripe_plan_id_string function to
obtain the name of the plan to be created
+ :param interval: str representing the interval of the Plan
+ Specifies billing frequency. Either day, week, month or year.
+ Ref: https://stripe.com/docs/api/plans/create#create_plan-interval
+ The default is month
:return: The StripePlan object if it exists else creates a
Plan object in Stripe and a local StripePlan and
returns it. Returns None in case of Stripe error
@@ -245,6 +250,7 @@ class StripeUtils(object):
_amount = float(amount)
amount = int(_amount * 100) # stripe amount unit, in cents
stripe_plan_db_obj = None
+ plan_interval = interval if interval is not "" else self.INTERVAL
try:
stripe_plan_db_obj = StripePlan.objects.get(
stripe_plan_id=stripe_plan_id)
@@ -252,7 +258,7 @@ class StripeUtils(object):
try:
self.stripe.Plan.create(
amount=amount,
- interval=self.INTERVAL,
+ interval=plan_interval,
name=name,
currency=self.CURRENCY,
id=stripe_plan_id)
From 435cfa46a6efcc03988a98e0b420e0edf5a69cf2 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 19:57:53 +0530
Subject: [PATCH 038/522] Change interval to year for that case in order
confirmation
---
datacenterlight/templates/datacenterlight/order_detail.html | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html
index 2aae3391..bc8e7562 100644
--- a/datacenterlight/templates/datacenterlight/order_detail.html
+++ b/datacenterlight/templates/datacenterlight/order_detail.html
@@ -154,7 +154,11 @@
{% if generic_payment_details %}
{% if generic_payment_details.recurring %}
-
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/month{% endblocktrans %}.
+ {% if generic_payment_details.recurring_interval == 'year' %}
+
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/year{% endblocktrans %}.
+ {% else %}
+
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/month{% endblocktrans %}.
+ {% endif %}
{% else %}
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this payment will charge your credit card account with a one time amount of {{total_price}} CHF{% endblocktrans %}.
{% endif %}
From b790676940728b1976bc51d5845e527e7b73e17c Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 20:07:26 +0530
Subject: [PATCH 039/522] Update datacenterlight's django.po
---
datacenterlight/locale/de/LC_MESSAGES/django.po | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po
index b5ff3ca5..165b527b 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-07-03 11:18+0000\n"
+"POT-Creation-Date: 2019-11-15 14:29+0000\n"
"PO-Revision-Date: 2018-03-30 23:22+0000\n"
"Last-Translator: b'Anonymous User '\n"
"Language-Team: LANGUAGE \n"
@@ -434,12 +434,20 @@ msgstr "Mehrwertsteuer"
#| msgid ""
#| "By clicking \"Place order\" this plan will charge your credit card "
#| "account with %(total_price)s CHF/month"
+msgid ""
+"By clicking \"Place order\" this plan will charge your credit card account "
+"with %(total_price)s CHF/year"
+msgstr ""
+"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
+"%(total_price)s CHF pro Jahr berechnet"
+
+
msgid ""
"By clicking \"Place order\" this plan will charge your credit card account "
"with %(total_price)s CHF/month"
msgstr ""
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
-"%(vm_total_price)s CHF pro Monat belastet"
+"%(total_price)s CHF pro Monat belastet"
#, fuzzy, python-format
#| msgid ""
From 93527fdc02aed0b70475dd1be5cf6443a55033ed Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 20:22:14 +0530
Subject: [PATCH 040/522] Update datacenterlight's django.po
---
.../locale/de/LC_MESSAGES/django.po | 21 ++++++++++++-------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po
index 165b527b..a704c53b 100644
--- a/datacenterlight/locale/de/LC_MESSAGES/django.po
+++ b/datacenterlight/locale/de/LC_MESSAGES/django.po
@@ -375,6 +375,9 @@ msgstr "Letzten"
msgid "Type"
msgstr "Typ"
+msgid "Expiry"
+msgstr "Ablaufdatum"
+
msgid "SELECT"
msgstr "AUSWÄHLEN"
@@ -415,6 +418,15 @@ msgstr "Bestellungsübersicht"
msgid "Product"
msgstr "Produkt"
+msgid "Price"
+msgstr "Preise"
+
+msgid "VAT for"
+msgstr ""
+
+msgid "Total Amount"
+msgstr "Gesamtsumme"
+
msgid "Amount"
msgstr ""
@@ -439,8 +451,7 @@ msgid ""
"with %(total_price)s CHF/year"
msgstr ""
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
-"%(total_price)s CHF pro Jahr berechnet"
-
+"%(total_price)s CHF pro Jahr belastet"
msgid ""
"By clicking \"Place order\" this plan will charge your credit card account "
@@ -652,9 +663,6 @@ msgstr ""
#~ msgid "Card Number"
#~ msgstr "Kreditkartennummer"
-#~ msgid "Expiry Date"
-#~ msgstr "Ablaufdatum"
-
#~ msgid ""
#~ "You are not making any payment yet. After placing your order, you will be "
#~ "taken to the Submit Payment Page."
@@ -663,9 +671,6 @@ msgstr ""
#~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt "
#~ "hast."
-#~ msgid "Pricing"
-#~ msgstr "Preise"
-
#~ msgid "Order VM"
#~ msgstr "VM bestellen"
From 6eef592cd80376d491ddbe6a8e58ae1144d7ed77 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 20:28:00 +0530
Subject: [PATCH 041/522] Add migration file
---
...icproduct_product_subscription_interval.py | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
create mode 100644 hosting/migrations/0058_genericproduct_product_subscription_interval.py
diff --git a/hosting/migrations/0058_genericproduct_product_subscription_interval.py b/hosting/migrations/0058_genericproduct_product_subscription_interval.py
new file mode 100644
index 00000000..c994ef16
--- /dev/null
+++ b/hosting/migrations/0058_genericproduct_product_subscription_interval.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2019-11-15 14:57
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('hosting', '0057_vatrates'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='genericproduct',
+ name='product_subscription_interval',
+ field=models.CharField(default='month', help_text='Choose between `year` and `month`', max_length=10),
+ ),
+ ]
From a423dd9f49858e1f226245ebb63c6af3ee026a0f Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 20:43:54 +0530
Subject: [PATCH 042/522] Correct invoice for yearly subscription
---
hosting/templates/hosting/invoice_detail.html | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index d757d476..a18af342 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -195,8 +195,13 @@
{% if invoice.order.subscription_id %}
{% trans "Recurring" %}:
- {{invoice.order.created_at|date:'d'|ordinal}}
+ {% if invoice.order.generic_product.product_subscription_interval == 'year' %}
+ {{invoice.order.created_at|date:'d'|ordinal}} {{invoice.order.created_at|date:'F'}}
+ {% trans "of every year" %}
+ {% else %}
+ {{invoice.order.created_at|date:'d'|ordinal}}
{% trans "of every month" %}
+ {% endif %}
{% endif %}
From 1e57eb5faeb0bb3160cbef226856c51950efe4ba Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 21:10:12 +0530
Subject: [PATCH 043/522] Handle TypeError raised in an invoice for generic
product
Case: No VM_ID exists and hence int(vm_id) raises TypeError
---
hosting/views.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/hosting/views.py b/hosting/views.py
index bb00978d..25303b99 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1268,6 +1268,10 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
context['vm']['total_price'] = (
price + vat - discount['amount']
)
+ except TypeError:
+ logger.error("Type error. Probably we "
+ "came from a generic product. "
+ "Invoice ID %s" % obj.invoice_id)
except WrongIdError:
logger.error("WrongIdError while accessing "
"invoice {}".format(obj.invoice_id))
From 5697e313df291806871dc57b63916e17aa35a73a Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 21:11:26 +0530
Subject: [PATCH 044/522] Improve yearly recurring date text
---
hosting/templates/hosting/invoice_detail.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index a18af342..1a0421f2 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -196,8 +196,8 @@
{% trans "Recurring" %}:
{% if invoice.order.generic_product.product_subscription_interval == 'year' %}
- {{invoice.order.created_at|date:'d'|ordinal}} {{invoice.order.created_at|date:'F'}}
- {% trans "of every year" %}
+ {{invoice.order.created_at|date:'d'|ordinal}} {% trans "of" %}{{invoice.order.created_at|date:'F'}}
+ {% trans "each year" %}
{% else %}
{{invoice.order.created_at|date:'d'|ordinal}}
{% trans "of every month" %}
From e726f953a448b4eb8f31bf65a29bcea70187e8de Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 21:13:08 +0530
Subject: [PATCH 045/522] Improve yearly recurring date text
---
hosting/templates/hosting/invoice_detail.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index 1a0421f2..5e206070 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -196,7 +196,7 @@
{% trans "Recurring" %}:
{% if invoice.order.generic_product.product_subscription_interval == 'year' %}
- {{invoice.order.created_at|date:'d'|ordinal}} {% trans "of" %}{{invoice.order.created_at|date:'F'}}
+ {{invoice.order.created_at|date:'d'|ordinal}} {% trans "of" %} {{invoice.order.created_at|date:'M'}}
{% trans "each year" %}
{% else %}
{{invoice.order.created_at|date:'d'|ordinal}}
From 530e47586e4e764ffbbdd231d524be1c62b824f2 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 21:23:04 +0530
Subject: [PATCH 046/522] Fix month name
---
hosting/templates/hosting/invoice_detail.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html
index 5e206070..b9a3e742 100644
--- a/hosting/templates/hosting/invoice_detail.html
+++ b/hosting/templates/hosting/invoice_detail.html
@@ -196,7 +196,7 @@
{% trans "Recurring" %}:
{% if invoice.order.generic_product.product_subscription_interval == 'year' %}
- {{invoice.order.created_at|date:'d'|ordinal}} {% trans "of" %} {{invoice.order.created_at|date:'M'}}
+ {{invoice.order.created_at|date:'d'|ordinal}} {% trans "of" %} {{invoice.order.created_at|date:'b'|title}}
{% trans "each year" %}
{% else %}
{{invoice.order.created_at|date:'d'|ordinal}}
From 7dd57fb1166ab813118724226a8374beceb8efea Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 22:04:37 +0530
Subject: [PATCH 047/522] Fix old order detail page
---
hosting/templates/hosting/order_detail.html | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html
index a84e4e4f..2775882d 100644
--- a/hosting/templates/hosting/order_detail.html
+++ b/hosting/templates/hosting/order_detail.html
@@ -186,7 +186,13 @@
{% if order.subscription_id %}
{% trans "Recurring" %}:
- {{order.created_at|date:'d'|ordinal}} {% trans "of every month" %}
+ {% if order.generic_product.product_subscription_interval == 'year' %}
+ {{order.created_at|date:'d'|ordinal}} {% trans "of" %} {{order.created_at|date:'b'|title}}
+ {% trans "each year" %}
+ {% else %}
+ {{order.created_at|date:'d'|ordinal}}
+ {% trans "of every month" %}
+ {% endif %}
{% endif %}
From aec2002a9f4b2c846a6aa7c76c1cc7dd89374fce Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 22:11:15 +0530
Subject: [PATCH 048/522] Update django.po
---
hosting/locale/de/LC_MESSAGES/django.po | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/hosting/locale/de/LC_MESSAGES/django.po b/hosting/locale/de/LC_MESSAGES/django.po
index 5c719457..2e65bb71 100644
--- a/hosting/locale/de/LC_MESSAGES/django.po
+++ b/hosting/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-09-15 03:39+0000\n"
+"POT-Creation-Date: 2019-11-15 16:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -48,6 +48,9 @@ msgstr "Produkt"
msgid "Monthly subscription"
msgstr ""
+msgid "Yearly subscription"
+msgstr ""
+
msgid "One time payment"
msgstr ""
@@ -239,7 +242,8 @@ msgid "You can view your VM detail by clicking the button below."
msgstr "Um die Rechnung zu sehen, klicke auf den Button unten."
msgid "You can log in to your VM by the username puffy."
-msgstr "Du kannst Dich auf Deiner VM mit dem user puffy einloggen."
+msgstr ""
+"Du kannst Dich auf Deiner VM mit dem user puffy einloggen."
msgid "View Detail"
msgstr "Details anzeigen"
@@ -405,6 +409,12 @@ msgstr ""
msgid "Recurring"
msgstr ""
+msgid "of"
+msgstr ""
+
+msgid "each year"
+msgstr ""
+
msgid "of every month"
msgstr ""
@@ -426,9 +436,6 @@ msgstr "Siehe Rechnung"
msgid "Page"
msgstr ""
-msgid "of"
-msgstr ""
-
msgid "Log in"
msgstr "Anmelden"
From dc507396ebe3aaa3ebc2984dc22a31587f5c6fbe Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 22:16:11 +0530
Subject: [PATCH 049/522] Update Changelog for 2.6.9
---
Changelog | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Changelog b/Changelog
index 8709d151..04b699a9 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,8 @@
+2.6.9: 2019-11-15
+ * feature: Allow creating yearly subscriptions for Generic Products (MR!718)
+ Notes for deployment:
+ - do a db migrate for new column added to Generic Product model
+ ./manage.py migrate hosting
2.6.8: 2019-11-15
* feature: [EU VAT] Add EU VAT feature for generic products (MR!717)
Notes for deployment:
From 15ef20dbc10c6187cc1af7bde4c99e3203ff0306 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 22:58:47 +0530
Subject: [PATCH 050/522] Fix yearly email
---
datacenterlight/views.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/datacenterlight/views.py b/datacenterlight/views.py
index af6627ec..44226abb 100644
--- a/datacenterlight/views.py
+++ b/datacenterlight/views.py
@@ -994,6 +994,9 @@ class OrderConfirmationView(DetailView, FormView):
'reply_to': [context['email']],
}
send_plain_email_task.delay(email_data)
+ recurring_text = _(" This is a monthly recurring plan.")
+ if gp_details['recurring_interval'] == "year":
+ recurring_text = _(" This is an yearly recurring plan.")
email_data = {
'subject': _("Confirmation of your payment"),
@@ -1007,7 +1010,7 @@ class OrderConfirmationView(DetailView, FormView):
name=user.get('name'),
amount=gp_details['amount'],
recurring=(
- _(' This is a monthly recurring plan.')
+ recurring_text
if gp_details['recurring'] else ''
)
)
From 1ff577ddcdc32e2db59987cd4f82e87c4f12d90c Mon Sep 17 00:00:00 2001
From: PCoder
Date: Fri, 15 Nov 2019 23:06:29 +0530
Subject: [PATCH 051/522] Update django.po
---
.../locale/de/LC_MESSAGES/django.po | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po
index a704c53b..7b381c16 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 14:29+0000\n"
+"POT-Creation-Date: 2019-11-15 17:33+0000\n"
"PO-Revision-Date: 2018-03-30 23:22+0000\n"
"Last-Translator: b'Anonymous User '\n"
"Language-Team: LANGUAGE \n"
@@ -450,15 +450,16 @@ msgid ""
"By clicking \"Place order\" this plan will charge your credit card account "
"with %(total_price)s CHF/year"
msgstr ""
-"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
-"%(total_price)s CHF pro Jahr belastet"
+"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s "
+"CHF pro Jahr belastet"
+
msgid ""
"By clicking \"Place order\" this plan will charge your credit card account "
"with %(total_price)s CHF/month"
msgstr ""
-"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
-"%(total_price)s CHF pro Monat belastet"
+"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s "
+"CHF pro Monat belastet"
#, fuzzy, python-format
#| msgid ""
@@ -617,10 +618,13 @@ msgid "An error occurred while associating the card. Details: {details}"
msgstr ""
"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
-msgid "Confirmation of your payment"
+msgid " This is a monthly recurring plan."
msgstr ""
-msgid " This is a monthly recurring plan."
+msgid " This is an yearly recurring plan."
+msgstr ""
+
+msgid "Confirmation of your payment"
msgstr ""
#, python-brace-format
From f82ed81b332a3f895035945c04862d0e898d739d Mon Sep 17 00:00:00 2001
From: _moep_
Date: Sat, 16 Nov 2019 08:30:44 +0100
Subject: [PATCH 052/522] add german translation
---
.../locale/de/LC_MESSAGES/django.po | 53 ++++++++++---------
1 file changed, 27 insertions(+), 26 deletions(-)
diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po
index 7b381c16..6d58673a 100644
--- a/datacenterlight/locale/de/LC_MESSAGES/django.po
+++ b/datacenterlight/locale/de/LC_MESSAGES/django.po
@@ -20,7 +20,7 @@ msgstr ""
"X-Translated-Using: django-rosetta 0.8.1\n"
msgid "CMS Favicon"
-msgstr ""
+msgstr "CMS Favicon"
#, python-format
msgid "Your New VM %(vm_name)s at Data Center Light"
@@ -52,7 +52,7 @@ msgid "Login"
msgstr "Anmelden"
msgid "Dashboard"
-msgstr ""
+msgstr "Dashboard"
msgid "Thank you for contacting us."
msgstr "Nachricht gesendet."
@@ -64,7 +64,7 @@ msgid "Get in touch with us!"
msgstr "Sende uns eine Nachricht."
msgid "Name"
-msgstr ""
+msgstr "Name"
msgid "Please enter your name."
msgstr "Bitte gib Deinen Namen ein."
@@ -108,7 +108,7 @@ msgid "Your account details are as follows"
msgstr "Deine Account Details sind unten aufgelistet"
msgid "Username"
-msgstr "Username"
+msgstr "Benusername"
msgid "Your email address"
msgstr "Deine E-Mail-Adresse"
@@ -155,7 +155,7 @@ msgid "Please enter a value in range %(min_ram)s - 200."
msgstr "Bitte gib einen Wert von %(min_ram)s bis 200 ein."
msgid "VM hosting"
-msgstr ""
+msgstr "VM Hosting"
msgid "month"
msgstr "Monat"
@@ -207,14 +207,14 @@ msgstr ""
msgid "Only wants you to pay for what you actually need."
msgstr ""
-"Möchte, dass du nur bezahlst, was du auch wirklich brauchst: Wähle deine "
+"Du möchtest nur das bezahlen, was du auch wirklich brauchst: Wähle deine "
"Ressourcen individuell aus!
"
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 ""
-"Ist kreativ, indem es sich ein modernes und alternatives Layout zu Nutze "
+"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.
"
@@ -222,9 +222,9 @@ msgid ""
"Cuts down the costs for you by using FOSS (Free Open Source Software) "
"exclusively, wherefore we can save money from paying licenses."
msgstr ""
-"Sorgt dafür, dass unnötige Kosten erspart werden, indem es ausschliesslich "
-"mit FOSS (Free Open Source Software) arbeitet und wir daher auf "
-"Lizenzgebühren verzichten können.
"
+"Um unnötige Kosten zu sparen werden, wird ausschliesslich Software auf"
+"Basis von FOSS (Free Open Source Software) eingesetzt und dadurch können auf "
+"Lizenzgebühren verzichtet werden.
"
msgid "Scale out"
msgstr "Skalierung"
@@ -311,7 +311,7 @@ msgid "Billing Address"
msgstr "Rechnungsadresse"
msgid "Make a payment"
-msgstr ""
+msgstr "Tätige eine Bezahlung"
msgid "Your Order"
msgstr "Deine Bestellung"
@@ -422,19 +422,19 @@ msgid "Price"
msgstr "Preise"
msgid "VAT for"
-msgstr ""
+msgstr "MwSt für"
msgid "Total Amount"
msgstr "Gesamtsumme"
msgid "Amount"
-msgstr ""
+msgstr "Betrag"
msgid "Description"
-msgstr ""
+msgstr "Beschreibung"
msgid "Recurring"
-msgstr ""
+msgstr "Wiederholend"
msgid "Subtotal"
msgstr "Zwischensumme"
@@ -490,10 +490,10 @@ msgid "Hold tight, we are processing your request"
msgstr "Bitte warten - wir verarbeiten Deine Anfrage gerade"
msgid "OK"
-msgstr ""
+msgstr "Ok"
msgid "Close"
-msgstr ""
+msgstr "Schliessen"
msgid "Some problem encountered. Please try again later."
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
@@ -505,7 +505,7 @@ msgid "Tech Stack"
msgstr "Tech Stack"
msgid "We are seriously open source."
-msgstr "Wir sind vollends opensource."
+msgstr "Wir sind vollends Open Source."
msgid ""
" Our full software stack is open source – We don't use anything that isn't "
@@ -575,13 +575,13 @@ msgid "Starting from only 15CHF per month. Try now."
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 "Tagen sagen mehr als Worte – Teste jetzt unsere VM!"
+msgstr "Taten sagen mehr als Worte – Teste jetzt unsere VM!"
msgid "Invalid number of cores"
msgstr "Ungültige Anzahle CPU-Kerne"
msgid "Invalid calculator properties"
-msgstr ""
+msgstr "Ungültige Berechnungseigenschaften"
msgid "Invalid RAM size"
msgstr "Ungültige RAM-Grösse"
@@ -591,7 +591,7 @@ msgstr "Ungültige Speicher-Grösse"
#, python-brace-format
msgid "Incorrect pricing name. Please contact support{support_email}"
-msgstr ""
+msgstr "Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}"
#, python-brace-format
msgid "{user} does not have permission to access the card"
@@ -619,13 +619,13 @@ msgstr ""
"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
msgid " This is a monthly recurring plan."
-msgstr ""
+msgstr "Dies ist ein monatlich wiederkehrender Plan."
msgid " This is an yearly recurring plan."
-msgstr ""
+msgstr "Dies ist ein jährlich wiederkehrender Plan."
msgid "Confirmation of your payment"
-msgstr ""
+msgstr "Bestätigung deiner Zahlung"
#, python-brace-format
msgid ""
@@ -636,7 +636,8 @@ msgid ""
"\n"
"Cheers,\n"
"Your Data Center Light team"
-msgstr ""
+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."
@@ -644,7 +645,7 @@ 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 ""
+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."
From 49ef761b2e2d856d88b63ccec5db524a73945f00 Mon Sep 17 00:00:00 2001
From: _moep_
Date: Sat, 16 Nov 2019 08:45:47 +0100
Subject: [PATCH 053/522] translate it, too
---
hosting/locale/de/LC_MESSAGES/django.po | 36 ++++++++++++-------------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/hosting/locale/de/LC_MESSAGES/django.po b/hosting/locale/de/LC_MESSAGES/django.po
index 2e65bb71..08bcdd7a 100644
--- a/hosting/locale/de/LC_MESSAGES/django.po
+++ b/hosting/locale/de/LC_MESSAGES/django.po
@@ -28,31 +28,31 @@ msgid "User does not exist"
msgstr "Der Benutzer existiert nicht"
msgid "Choose a product"
-msgstr ""
+msgstr "Wähle ein Produkt"
msgid "Amount in CHF"
msgstr "Betrag"
msgid "Recurring monthly"
-msgstr ""
+msgstr "monatlich wiederkehrend"
msgid "Amount field does not match"
-msgstr ""
+msgstr "Betragsfeld stimmt nicht überein"
msgid "Recurring field does not match"
-msgstr ""
+msgstr "Betragsfeld stimmt nicht überein"
msgid "Product name"
msgstr "Produkt"
msgid "Monthly subscription"
-msgstr ""
+msgstr "Monatliches Abonnement"
msgid "Yearly subscription"
-msgstr ""
+msgstr "Jährliches Abonnement"
msgid "One time payment"
-msgstr ""
+msgstr "Einmalzahlung"
msgid "Confirm Password"
msgstr "Passwort Bestätigung"
@@ -76,7 +76,7 @@ msgid "Please input a proper SSH key"
msgstr "Bitte verwende einen gültigen SSH-Key"
msgid "Comma not accepted in the name of the key"
-msgstr ""
+msgstr "Komma im Namen des Keys wird nicht akzeptiert"
msgid "All Rights Reserved"
msgstr "Alle Rechte vorbehalten"
@@ -404,19 +404,19 @@ msgid "Amount"
msgstr "Betrag"
msgid "Description"
-msgstr ""
+msgstr "Beschreibung"
msgid "Recurring"
-msgstr ""
+msgstr "wiederkehrend"
msgid "of"
-msgstr ""
+msgstr "von"
msgid "each year"
-msgstr ""
+msgstr "jedes Jahr"
msgid "of every month"
-msgstr ""
+msgstr "jeden Monat"
msgid "BACK TO LIST"
msgstr "ZURÜCK ZUR LISTE"
@@ -428,13 +428,13 @@ msgid "VM ID"
msgstr ""
msgid "IP Address"
-msgstr ""
+msgstr "IP-Adresse"
msgid "See Invoice"
msgstr "Siehe Rechnung"
msgid "Page"
-msgstr ""
+msgstr "Seite"
msgid "Log in"
msgstr "Anmelden"
@@ -496,7 +496,7 @@ msgid "Hold tight, we are processing your request"
msgstr "Bitte warten - wir bearbeiten Deine Anfrage gerade"
msgid "OK"
-msgstr ""
+msgstr "Ok"
msgid "Close"
msgstr "Schliessen"
@@ -852,7 +852,7 @@ msgstr "Ungültige Speicher-Grösse"
#, python-brace-format
msgid "Incorrect pricing name. Please contact support{support_email}"
-msgstr ""
+msgstr "Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}"
msgid ""
"We could not find the requested VM. Please "
@@ -871,7 +871,7 @@ msgstr "Fehler beenden VM"
msgid ""
"VM terminate action timed out. Please contact support@datacenterlight.ch for "
"further information."
-msgstr ""
+msgstr "VM beendet wegen Zeitüberschreitung. Bitte kontaktiere support@datacenterlight.ch für weitere Informationen."
#, python-format
msgid "Virtual Machine %(vm_name)s Cancelled"
From 67d38df047cc2d412f9ff78f81a8499c9f6fbf15 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Sat, 16 Nov 2019 20:41:24 +0530
Subject: [PATCH 054/522] Correction by Sanghee: Benusername -> Benutzername
---
datacenterlight/locale/de/LC_MESSAGES/django.po | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po
index 6d58673a..2a76c118 100644
--- a/datacenterlight/locale/de/LC_MESSAGES/django.po
+++ b/datacenterlight/locale/de/LC_MESSAGES/django.po
@@ -108,7 +108,7 @@ msgid "Your account details are as follows"
msgstr "Deine Account Details sind unten aufgelistet"
msgid "Username"
-msgstr "Benusername"
+msgstr "Benutzername"
msgid "Your email address"
msgstr "Deine E-Mail-Adresse"
From 3d28b17c717b337a1d66bbb0dd048879db04bb55 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Sat, 16 Nov 2019 20:44:30 +0530
Subject: [PATCH 055/522] Update Changelog for 2.6.10
---
Changelog | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Changelog b/Changelog
index 04b699a9..43c8ebc3 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,5 @@
+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
* feature: Allow creating yearly subscriptions for Generic Products (MR!718)
Notes for deployment:
From 9a84fc899e80faf106a1f6063839270ad21d4de4 Mon Sep 17 00:00:00 2001
From: PCoder
Date: Thu, 28 Nov 2019 12:10:40 +0530
Subject: [PATCH 056/522] 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 057/522] 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 058/522] 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 059/522] 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 060/522] 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 061/522] 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 062/522] 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 063/522] 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 064/522] 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 065/522] 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 066/522] 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 067/522] 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 068/522] 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 069/522] 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 070/522] 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 071/522] 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 072/522] 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 073/522] 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 074/522] 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 075/522] 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 076/522] 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 077/522] 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 078/522] 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 079/522] 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 080/522] 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 081/522] 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 082/522] 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 083/522] 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 084/522] 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 085/522] 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 086/522] 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 087/522] 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 088/522] 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 089/522] 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 090/522] 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 091/522] 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 092/522] 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 093/522] 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 094/522] 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}}
+