From b8ca7286f2e2767a56e0b21f24bd3f25d2abdbbf Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Thu, 9 May 2019 01:34:18 -0400 Subject: [PATCH 001/626] Add view to check if the vm belongs to a user --- hosting/urls.py | 3 ++- hosting/views.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/hosting/urls.py b/hosting/urls.py index a3579f06..1d0033ef 100644 --- a/hosting/urls.py +++ b/hosting/urls.py @@ -9,13 +9,14 @@ from .views import ( HostingPricingView, CreateVirtualMachinesView, HostingBillListView, HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView, SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView, - InvoiceListView, InvoiceDetailView + InvoiceListView, InvoiceDetailView, CheckUserVM ) urlpatterns = [ url(r'index/?$', IndexView.as_view(), name='index'), url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'), + url(r'checkvm/?$', CheckUserVM.as_view(), name='chech_vm'), url(r'dashboard/?$', DashboardView.as_view(), name='dashboard'), url(r'nodejs/?$', NodeJSHostingView.as_view(), name='nodejshosting'), url(r'rails/?$', RailsHostingView.as_view(), name='railshosting'), diff --git a/hosting/views.py b/hosting/views.py index 92dd5aa8..4c0d8b41 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -26,6 +26,8 @@ from django.views.generic import ( View, CreateView, FormView, ListView, DetailView, DeleteView, TemplateView, UpdateView ) +from rest_framework.views import APIView +from rest_framework.response import Response from guardian.mixins import PermissionRequiredMixin from oca.pool import WrongIdError from stored_messages.api import mark_read @@ -1755,3 +1757,21 @@ def forbidden_view(request, exception=None, reason=''): 'again.') messages.add_message(request, messages.ERROR, err_msg) return HttpResponseRedirect(request.get_full_path()) + + +class CheckUserVM(APIView): + + def get(self, request): + try: + email = request.data['email'] + ip = request.data['ip'] + uservms = VMDetail.objects.filter(user__email=email) + if len(uservms) > 0: + for i in range(len(uservms)): + if uservms[i].ipv4 == ip or uservms[i].ipv6 == ip: + return Response('success', 200) + return Response('No VM found matching the ip address provided', 403) + else: + return Response('No VM found with the given email address', 403) + except KeyError: + return Response('Not enough data provided', 400) From cd47af23f1dc0ff9598ef0ac0504fffed80e7167 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 10 May 2019 07:22:21 +0200 Subject: [PATCH 002/626] Update Changelog --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index f7be3e53..ec77e55b 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +Next: + * #6670: [hosting/save_ssh_key] Upgrade cdist version to 5.0.1 to manage keys on Alpine linux 2.5.9: 2019-05-09 * #6669: [hosting] Fix opennebula vm query takes long (MR!703) * [hosting] Increase VMDetail model's configuration parameter length to 128 (MR!702) From 1d70563ea209501f90022809e937ebc6e5b96fb7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 10 May 2019 09:19:31 +0200 Subject: [PATCH 003/626] Save user's key in opennebula --- datacenterlight/tasks.py | 49 +--------------------------------------- hosting/views.py | 13 +++++++---- opennebula_api/models.py | 25 ++++++++++++++++++++ 3 files changed, 35 insertions(+), 52 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 5f12b7df..12c1cf38 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -197,54 +197,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): get_or_create_vm_detail(custom_user, manager, vm_id) if custom_user is not None: public_keys = get_all_public_keys(custom_user) - keys = [{'value': key, 'state': True} for key in - public_keys] - if len(keys) > 0: - logger.debug( - "Calling configure on {host} for " - "{num_keys} keys".format( - host=vm_ipv6, num_keys=len(keys) - ) - ) - # Let's wait until the IP responds to ping before we - # run the cdist configure on the host - did_manage_public_key = False - for i in range(0, 15): - if ping_ok(vm_ipv6): - logger.debug( - "{} is pingable. Doing a " - "manage_public_key".format(vm_ipv6) - ) - sleep(10) - manager.manage_public_key( - keys, hosts=[vm_ipv6] - ) - did_manage_public_key = True - break - else: - logger.debug( - "Can't ping {}. Wait 5 secs".format( - vm_ipv6 - ) - ) - sleep(5) - if not did_manage_public_key: - emsg = ("Waited for over 75 seconds for {} to be " - "pingable. But the VM was not reachable. " - "So, gave up manage_public_key. Please do " - "this manually".format(vm_ipv6)) - logger.error(emsg) - email_data = { - 'subject': '{} CELERY TASK INCOMPLETE: {} not ' - 'pingable for 75 seconds'.format( - settings.DCL_TEXT, vm_ipv6 - ), - 'from_email': current_task.request.hostname, - 'to': settings.DCL_ERROR_EMAILS_TO_LIST, - 'body': emsg - } - email = EmailMessage(**email_data) - email.send() + manager.save_key_in_opennebula('\n'.join(public_keys)) else: logger.debug("VM's ipv6 is None. Hence not created VMDetail") except Exception as e: diff --git a/hosting/views.py b/hosting/views.py index 92dd5aa8..209df30b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -36,6 +36,7 @@ from datacenterlight.cms_models import DCLCalculatorPluginModel from datacenterlight.models import VMTemplate, VMPricing from datacenterlight.utils import create_vm, get_cms_integration from hosting.models import UserCardDetail +from utils.hosting_utils import get_all_public_keys from membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import ( @@ -460,7 +461,9 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView): pk = self.kwargs.get('pk') # Get user ssh key public_key = UserHostingKey.objects.get(pk=pk).public_key - manager.manage_public_key([{'value': public_key, 'state': False}]) + keys = UserHostingKey.objects.filter(user=self.request.user) + keys_to_save = [k.public_key for k in keys if k != public_key] + manager.save_key_in_opennebula('\n'.join(keys_to_save)) return super(SSHKeyDeleteView, self).delete(request, *args, **kwargs) @@ -509,8 +512,8 @@ class SSHKeyChoiceView(LoginRequiredMixin, View): email=owner.email, password=owner.password ) - public_key_str = public_key.decode() - manager.manage_public_key([{'value': public_key_str, 'state': True}]) + keys = get_all_public_keys(request.user) + manager.save_key_in_opennebula('\n'.join(keys)) return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar') @@ -563,7 +566,9 @@ class SSHKeyCreateView(LoginRequiredMixin, FormView): public_key = form.cleaned_data['public_key'] if type(public_key) is bytes: public_key = public_key.decode() - manager.manage_public_key([{'value': public_key, 'state': True}]) + keys = UserHostingKey.objects.filter(user=self.request.user) + keys_to_save = [k.public_key for k in keys] + manager.save_key_in_opennebula('\n'.join(keys_to_save)) return HttpResponseRedirect(self.success_url) def post(self, request, *args, **kwargs): diff --git a/opennebula_api/models.py b/opennebula_api/models.py index a333aa23..5a838c02 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -371,6 +371,31 @@ class OpenNebulaManager(): return vm_terminated + def save_key_in_opennebula(self, ssh_key): + """ + Save the given ssh key in OpenNebula user + + # Update type: 0: Replace the whole template. + 1: Merge new template with the existing one. + :param ssh_key: The ssh key to be saved + :return: + """ + UPDATE_TYPE = 1 + return_value = self.client.call( + 'one.user.update', + self.email, + self.password, + '%s' % ssh_key, + UPDATE_TYPE + ) + if type(return_value) == int: + logger.debug( + "Saved the key in opennebula successfully : %s" % return_value) + else: + logger.error( + "Could not save the key in opennebula. %s" % return_value) + return + def _get_template_pool(self): try: template_pool = oca.VmTemplatePool(self.oneadmin_client) From c92b8c6facdff3e8f82cb91475ae14afbe2ac107 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 10 May 2019 23:51:05 +0200 Subject: [PATCH 004/626] one. is appended by oca --- opennebula_api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 5a838c02..8564332c 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -382,7 +382,7 @@ class OpenNebulaManager(): """ UPDATE_TYPE = 1 return_value = self.client.call( - 'one.user.update', + 'user.update', self.email, self.password, '%s' % ssh_key, From 85136d80ccea0ee2e13b55677a2bd7e0febc04a8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 10 May 2019 23:57:52 +0200 Subject: [PATCH 005/626] Pass the opennebula user id as the object id --- opennebula_api/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 8564332c..8b4517e5 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -383,8 +383,7 @@ class OpenNebulaManager(): UPDATE_TYPE = 1 return_value = self.client.call( 'user.update', - self.email, - self.password, + self.opennebula_user.id, '%s' % ssh_key, UPDATE_TYPE ) From 6a1faa52e470a3923a331ce45ad99df84a9b9d72 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 00:25:49 +0200 Subject: [PATCH 006/626] Set user's own ssh keys when creating VM --- datacenterlight/tasks.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 12c1cf38..a4d2ebe7 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -79,10 +79,12 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): # Create OpenNebulaManager manager = OpenNebulaManager(email=on_user, password=on_pass) + custom_user = CustomUser.objects.get(email=user.get('email')) + pub_keys = get_all_public_keys(custom_user) vm_id = manager.create_vm( template_id=vm_template_id, specs=specs, - ssh_key=settings.ONEADMIN_USER_SSH_PUBLIC_KEY, + ssh_key='\n'.join(pub_keys), vm_name=vm_name ) @@ -193,7 +195,6 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): vm_ipv6 = manager.get_ipv6(vm_id) logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) if vm_ipv6 is not None: - custom_user = CustomUser.objects.get(email=user.get('email')) get_or_create_vm_detail(custom_user, manager, vm_id) if custom_user is not None: public_keys = get_all_public_keys(custom_user) From 5146daa6806152d5d100f8da6d095b856f562d2b Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 00:31:25 +0200 Subject: [PATCH 007/626] Use SSH_PUBLIC_KEY within CONTEXT --- opennebula_api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 8b4517e5..7873413b 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -384,7 +384,7 @@ class OpenNebulaManager(): return_value = self.client.call( 'user.update', self.opennebula_user.id, - '%s' % ssh_key, + '%s' % ssh_key, UPDATE_TYPE ) if type(return_value) == int: From 3602bb0eb7e98a02bc1d32e65b55cfeb4240d0d5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 01:46:28 +0200 Subject: [PATCH 008/626] Add save_key_in_vm_template and get_all_vmids methods --- opennebula_api/models.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 7873413b..61d08d1a 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -371,7 +371,31 @@ class OpenNebulaManager(): return vm_terminated - def save_key_in_opennebula(self, ssh_key): + def save_key_in_vm_template(self, vm_id, ssh_key): + """ + Update the template of a given VM and set the ssh key of the user + :param vm_id: the identifier of the VM object + :param ssh_key: a newline(\n) separated ssh key string that needs to be + set in the VM template + :return: + """ + UPDATE_TYPE = 1 + return_value = self.client.call( + 'vm.updateconf', + vm_id, + '%s' % ssh_key, + UPDATE_TYPE + ) + if type(return_value) == int: + logger.debug( + "Saved the key in VM Template success : %s" % return_value) + else: + logger.error( + "Could not save the key in VM Template. %s" % return_value) + + return + + def save_key_in_opennebula_user(self, ssh_key): """ Save the given ssh key in OpenNebula user @@ -613,6 +637,19 @@ class OpenNebulaManager(): "Keys and/or hosts are empty, so not managing any keys" ) + def get_all_vmids(self): + owner = CustomUser.objects.filter(email=self.email).first() + all_orders = HostingOrder.objects.filter(customer__user=owner) + vm_ids = [] + if len(all_orders) > 0: + logger.debug("The user {} has 1 or more VMs. We need to configure " + "the ssh keys.".format(self.email)) + vm_ids = [order.vm_id for order in all_orders] + else: + logger.debug("The user {} has no VMs. We don't need to configure " + "the ssh keys.".format(self.email)) + return vm_ids + def get_all_hosts(self): """ A utility function to obtain all hosts of this owner From 65c9ccb671fa8e3d690b4cf01b47a99c8c72234c Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 01:54:35 +0200 Subject: [PATCH 009/626] Use save_key_in_opennebula_user and save_key_in_vm_template --- hosting/views.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 209df30b..450875e5 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -463,7 +463,11 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView): public_key = UserHostingKey.objects.get(pk=pk).public_key keys = UserHostingKey.objects.filter(user=self.request.user) keys_to_save = [k.public_key for k in keys if k != public_key] - manager.save_key_in_opennebula('\n'.join(keys_to_save)) + manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) + vm_ids = manager.get_all_vmids() + if len(vm_ids) > 0 and len(keys_to_save) > 0: + for vm_id in vm_ids: + manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) return super(SSHKeyDeleteView, self).delete(request, *args, **kwargs) @@ -513,7 +517,11 @@ class SSHKeyChoiceView(LoginRequiredMixin, View): password=owner.password ) keys = get_all_public_keys(request.user) - manager.save_key_in_opennebula('\n'.join(keys)) + vm_ids = manager.get_all_vmids() + manager.save_key_in_opennebula_user('\n'.join(keys)) + if len(vm_ids) > 0 and len(keys) > 0: + for vm_id in vm_ids: + manager.save_key_in_vm_template(vm_id, '\n'.join(keys)) return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar') @@ -563,12 +571,13 @@ class SSHKeyCreateView(LoginRequiredMixin, FormView): email=owner.email, password=owner.password ) - public_key = form.cleaned_data['public_key'] - if type(public_key) is bytes: - public_key = public_key.decode() keys = UserHostingKey.objects.filter(user=self.request.user) keys_to_save = [k.public_key for k in keys] - manager.save_key_in_opennebula('\n'.join(keys_to_save)) + manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) + vm_ids = manager.get_all_vmids() + if len(vm_ids) > 0 and len(keys) > 0: + for vm_id in vm_ids: + manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) return HttpResponseRedirect(self.success_url) def post(self, request, *args, **kwargs): From 0b85784fd3fa5726fadd63c56fdd9d718d2d6058 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 01:56:00 +0200 Subject: [PATCH 010/626] No need to manage ssh keys after VM is created The ssh keys are added at the time the VM is created or later --- datacenterlight/tasks.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index a4d2ebe7..808f6c61 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -190,17 +190,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): email = BaseEmail(**email_data) email.send() - # try to see if we have the IPv6 of the new vm and that if the ssh - # keys can be configured - vm_ipv6 = manager.get_ipv6(vm_id) logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) - if vm_ipv6 is not None: - get_or_create_vm_detail(custom_user, manager, vm_id) - if custom_user is not None: - public_keys = get_all_public_keys(custom_user) - manager.save_key_in_opennebula('\n'.join(public_keys)) - else: - logger.debug("VM's ipv6 is None. Hence not created VMDetail") + get_or_create_vm_detail(custom_user, manager, vm_id) except Exception as e: logger.error(str(e)) try: From 7f6d4c1c53304aa80d032d1df1ed20001e7aca21 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 02:23:51 +0200 Subject: [PATCH 011/626] Refactor get_all_vmids -> get_all_active_vmids We now get this info from opennebula --- hosting/views.py | 6 +++--- opennebula_api/models.py | 31 ++++++++----------------------- 2 files changed, 11 insertions(+), 26 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 450875e5..f1a6dd9c 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -464,7 +464,7 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView): keys = UserHostingKey.objects.filter(user=self.request.user) keys_to_save = [k.public_key for k in keys if k != public_key] manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) - vm_ids = manager.get_all_vmids() + vm_ids = manager.get_vms() if len(vm_ids) > 0 and len(keys_to_save) > 0: for vm_id in vm_ids: manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) @@ -517,7 +517,7 @@ class SSHKeyChoiceView(LoginRequiredMixin, View): password=owner.password ) keys = get_all_public_keys(request.user) - vm_ids = manager.get_all_vmids() + vm_ids = manager.get_all_active_vmids() manager.save_key_in_opennebula_user('\n'.join(keys)) if len(vm_ids) > 0 and len(keys) > 0: for vm_id in vm_ids: @@ -574,7 +574,7 @@ class SSHKeyCreateView(LoginRequiredMixin, FormView): keys = UserHostingKey.objects.filter(user=self.request.user) keys_to_save = [k.public_key for k in keys] manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) - vm_ids = manager.get_all_vmids() + vm_ids = manager.get_all_active_vmids() if len(vm_ids) > 0 and len(keys) > 0: for vm_id in vm_ids: manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 61d08d1a..819bd803 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -168,7 +168,7 @@ class OpenNebulaManager(): raise return user_pool - def _get_vm_pool(self, infoextended=True): + def _get_vm_pool(self, infoextended=True, vm_state=-1): """ # filter: # -4: Resources belonging to the user’s primary group @@ -201,8 +201,8 @@ class OpenNebulaManager(): vm_pool = oca.VirtualMachinePool(self.client) if infoextended: vm_pool.infoextended( - filter=-1, # User's resources and any of his groups - vm_state=-1 # Look for VMs in any state, except DONE + filter=-1, # User's resources and any of his groups + vm_state=vm_state ) else: vm_pool.info() @@ -210,13 +210,6 @@ class OpenNebulaManager(): except AttributeError: logger.error( 'Could not connect via client, using oneadmin instead') - try: - vm_pool = oca.VirtualMachinePool(self.oneadmin_client) - vm_pool.info(filter=-2) - return vm_pool - except: - raise ConnectionRefusedError - except ConnectionRefusedError: logger.error( 'Could not connect to host: {host} via protocol {protocol}'.format( @@ -228,9 +221,9 @@ class OpenNebulaManager(): except: raise ConnectionRefusedError - def get_vms(self): + def get_vms(self, vm_state=-1): try: - return self._get_vm_pool() + return self._get_vm_pool(vm_state=vm_state) except ConnectionRefusedError: raise ConnectionRefusedError @@ -637,17 +630,9 @@ class OpenNebulaManager(): "Keys and/or hosts are empty, so not managing any keys" ) - def get_all_vmids(self): - owner = CustomUser.objects.filter(email=self.email).first() - all_orders = HostingOrder.objects.filter(customer__user=owner) - vm_ids = [] - if len(all_orders) > 0: - logger.debug("The user {} has 1 or more VMs. We need to configure " - "the ssh keys.".format(self.email)) - vm_ids = [order.vm_id for order in all_orders] - else: - logger.debug("The user {} has no VMs. We don't need to configure " - "the ssh keys.".format(self.email)) + def get_all_active_vmids(self): + vm_pool = self.get_vms(vm_state=3) # get active vms of the user + vm_ids = [vm.id for vm in vm_pool] return vm_ids def get_all_hosts(self): From b189371a7b6050fb9854b4f6bbfb507d5073ee8f Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 02:38:16 +0200 Subject: [PATCH 012/626] Call get_all_active_vmids to get the active vmids --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index f1a6dd9c..5a3e06b2 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -464,7 +464,7 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView): keys = UserHostingKey.objects.filter(user=self.request.user) keys_to_save = [k.public_key for k in keys if k != public_key] manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) - vm_ids = manager.get_vms() + vm_ids = manager.get_all_active_vmids() if len(vm_ids) > 0 and len(keys_to_save) > 0: for vm_id in vm_ids: manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) From 3133bde0e9879909aa68fcd4fe3fd4b18f15f150 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 11 May 2019 09:15:08 +0200 Subject: [PATCH 013/626] Don't set the key in the live template --- hosting/views.py | 15 +-------------- opennebula_api/models.py | 20 +++++++------------- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 5a3e06b2..dc09644e 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -464,10 +464,6 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView): keys = UserHostingKey.objects.filter(user=self.request.user) keys_to_save = [k.public_key for k in keys if k != public_key] manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) - vm_ids = manager.get_all_active_vmids() - if len(vm_ids) > 0 and len(keys_to_save) > 0: - for vm_id in vm_ids: - manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) return super(SSHKeyDeleteView, self).delete(request, *args, **kwargs) @@ -517,11 +513,7 @@ class SSHKeyChoiceView(LoginRequiredMixin, View): password=owner.password ) keys = get_all_public_keys(request.user) - vm_ids = manager.get_all_active_vmids() manager.save_key_in_opennebula_user('\n'.join(keys)) - if len(vm_ids) > 0 and len(keys) > 0: - for vm_id in vm_ids: - manager.save_key_in_vm_template(vm_id, '\n'.join(keys)) return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar') @@ -571,13 +563,8 @@ class SSHKeyCreateView(LoginRequiredMixin, FormView): email=owner.email, password=owner.password ) - keys = UserHostingKey.objects.filter(user=self.request.user) - keys_to_save = [k.public_key for k in keys] + keys_to_save = get_all_public_keys(self.request.user) manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) - vm_ids = manager.get_all_active_vmids() - if len(vm_ids) > 0 and len(keys) > 0: - for vm_id in vm_ids: - manager.save_key_in_vm_template(vm_id, '\n'.join(keys_to_save)) return HttpResponseRedirect(self.success_url) def post(self, request, *args, **kwargs): diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 819bd803..01bd065a 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -168,7 +168,7 @@ class OpenNebulaManager(): raise return user_pool - def _get_vm_pool(self, infoextended=True, vm_state=-1): + def _get_vm_pool(self, infoextended=True): """ # filter: # -4: Resources belonging to the user’s primary group @@ -201,15 +201,14 @@ class OpenNebulaManager(): vm_pool = oca.VirtualMachinePool(self.client) if infoextended: vm_pool.infoextended( - filter=-1, # User's resources and any of his groups - vm_state=vm_state + filter=-1, # User's resources and any of his groups + vm_state=-1 # Look for VMs in any state, except DONE ) else: vm_pool.info() return vm_pool - except AttributeError: - logger.error( - 'Could not connect via client, using oneadmin instead') + except AttributeError as ae: + logger.error("AttributeError : %s" % str(ae)) except ConnectionRefusedError: logger.error( 'Could not connect to host: {host} via protocol {protocol}'.format( @@ -221,9 +220,9 @@ class OpenNebulaManager(): except: raise ConnectionRefusedError - def get_vms(self, vm_state=-1): + def get_vms(self): try: - return self._get_vm_pool(vm_state=vm_state) + return self._get_vm_pool() except ConnectionRefusedError: raise ConnectionRefusedError @@ -630,11 +629,6 @@ class OpenNebulaManager(): "Keys and/or hosts are empty, so not managing any keys" ) - def get_all_active_vmids(self): - vm_pool = self.get_vms(vm_state=3) # get active vms of the user - vm_ids = [vm.id for vm in vm_pool] - return vm_ids - def get_all_hosts(self): """ A utility function to obtain all hosts of this owner From 7e538bf37b86ddd8ffdafff495e9bad04ca4be7b Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 12 May 2019 19:12:34 +0200 Subject: [PATCH 014/626] Add ssh_key_added_to_vm.{html,txt} email templates --- .../hosting/emails/ssh_key_added_to_vm.html | 51 +++++++++++++++++++ .../hosting/emails/ssh_key_added_to_vm.txt | 11 ++++ 2 files changed, 62 insertions(+) create mode 100644 hosting/templates/hosting/emails/ssh_key_added_to_vm.html create mode 100644 hosting/templates/hosting/emails/ssh_key_added_to_vm.txt diff --git a/hosting/templates/hosting/emails/ssh_key_added_to_vm.html b/hosting/templates/hosting/emails/ssh_key_added_to_vm.html new file mode 100644 index 00000000..086c3479 --- /dev/null +++ b/hosting/templates/hosting/emails/ssh_key_added_to_vm.html @@ -0,0 +1,51 @@ +{% load static i18n %} + + + + + + + {% blocktrans %}SSH key(s) {{keys}} added to your VM {{vm_name}}{% endblocktrans %} + + + + + + + + + + + + + + + + + + + + + +
+ +
+

{% blocktrans %}SSH keys on your VM {{ vm_name }}{% endblocktrans %}

+
+

+ {% blocktrans %}You initiated adding the keys {{keys}} to your VM {{ vm_name }}!{% endblocktrans %} +

+

+ {% blocktrans %}This email is to notify you that it was accomplished successfully.{% endblocktrans %} +

+

+ {% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %} +

+
+ {% trans "View Detail" %} +
+

{% trans "Your Data Center Light Team" %}

+
+ + + \ No newline at end of file diff --git a/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt b/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt new file mode 100644 index 00000000..ede56e3a --- /dev/null +++ b/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt @@ -0,0 +1,11 @@ +{% load i18n %} + +{% blocktrans %}{% blocktrans %}SSH key(s) {{keys}} added to your VM {{vm_name}}{% endblocktrans %} + +{% blocktrans %}You initiated adding the keys {{keys}} to your VM {{ vm_name }}!{% endblocktrans %} +{% blocktrans %}This email is to notify you that it was accomplished successfully.{% endblocktrans %} +{% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %} + +{{ base_url }}{{ vm_detail_url }} + +{% trans "Your Data Center Light Team" %} \ No newline at end of file From 67d789ebdb5f23058a34807c0193ad235d6559a7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 12 May 2019 19:13:22 +0200 Subject: [PATCH 015/626] Implement save_ssh_key_in_vm_template_task A celery task which first sends a power off signal to the VM with the given ID and polls until it is powered off. Then, it updates the VM template with the given ssh keys of the user and resumes it. User is notified once the VM template has been updated. --- datacenterlight/tasks.py | 87 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 808f6c61..6bf14825 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -1,4 +1,5 @@ from datetime import datetime +from time import sleep from celery import current_task from celery.exceptions import MaxRetriesExceededError @@ -8,7 +9,6 @@ from django.core.mail import EmailMessage from django.core.urlresolvers import reverse from django.utils import translation from django.utils.translation import ugettext_lazy as _ -from time import sleep from dynamicweb.celery import app from hosting.models import HostingOrder @@ -16,7 +16,7 @@ from membership.models import CustomUser from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import VirtualMachineSerializer from utils.hosting_utils import ( - get_all_public_keys, get_or_create_vm_detail, ping_ok + get_all_public_keys, get_or_create_vm_detail ) from utils.mailer import BaseEmail from utils.stripe_utils import StripeUtils @@ -176,7 +176,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): kwargs={'pk': order_id}), 'page_header': _( 'Your New VM %(vm_name)s at Data Center Light') % { - 'vm_name': vm.get('name')}, + 'vm_name': vm.get('name')}, 'vm_name': vm.get('name') } email_data = { @@ -213,3 +213,84 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): return return vm_id + + +@app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES) +def save_ssh_key_in_vm_template_task(self, user, vm_id, ssh_key_str): + logger.debug("Inside save_ssh_key_in_vm_template_task %s" % vm_id) + + on_user = user.get('email') + on_pass = user.get('pass') + + if on_user is None or on_pass is None: + logger.error( + "Either email or password not supplied. Can't save ssh key" + ) + return + + manager = OpenNebulaManager(email=on_user, password=on_pass) + + # poweroff the vm + vm = manager.power_off_vm(vm_id) + + powered_off = False + for t in range(15): + vm = manager.get_vm(vm_id) + if vm.str_state == 'POWEROFF': + logger.debug( + "VM %s has been powered off. Now adding ssh keys" % vm.id + ) + powered_off = True + break + else: + logger.debug( + "VM {} has state {}. Waiting 2 more seconds to see if it " + "powers off".format(vm.id, vm.str_state) + ) + sleep(2) + + if powered_off: + logger.debug( + "VM %s was powered off by api call" % vm.id + ) + if manager.save_key_in_vm_template(vm_id=vm_id, ssh_key=ssh_key_str) > 0: + logger.debug( + "Added ssh_keys of user %s to VM %s successfully" % + (on_user, vm_id) + ) + manager.resume(vm_id) + lang = 'en-us' + if user.get('language') is not None: + logger.debug( + "Language is set to {}".format(user.get('language'))) + lang = user.get('language') + translation.activate(lang) + # Send notification to the user as soon as VM has been booked + context = { + 'page_header': str(_("Adding of SSH key completed")), + 'base_url': "{0}://{1}".format(user.get('request_scheme'), + user.get('request_host')), + 'vm_detail_url': reverse('hosting:virtual_machines', + kwargs={'pk': vm_id}), + 'vm_name': vm.name + } + email_data = { + 'subject': context.get('page_header'), + 'to': user.get('email'), + 'context': context, + 'template_name': 'new_booked_vm', + 'template_path': 'hosting/emails/', + 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, + } + email = BaseEmail(**email_data) + email.send() + else: + logger.error( + "There was an error updating ssh keys of the VM %s" % vm_id + ) + else: + logger.error( + "VM {} did not poweroff within 30 seconds after the poweroff api " + "call. Please, ask the admin to poweroff and add the key " + "manually.".format(vm_id) + ) From 61127e56ca84c530d79909a06375164e67ef4c6c Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 12 May 2019 19:16:53 +0200 Subject: [PATCH 016/626] Update virtual_machine_detail.html template To show the Add SSH key button and the modal that pops up after clicking it. --- .../hosting/virtual_machine_detail.html | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index ce02036f..8b6537fe 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -80,6 +80,21 @@ + {% if keys|length > 0 %} + {% if not virtual_machine.status == 'canceled' %} +
+ +
+ {% trans "Sorry, there was an unexpected error. Kindly retry." %} +
+
+ {% endif %} + {% else %} + + {% endif %} +

{% trans "Support / Contact" %}

@@ -98,6 +113,41 @@ {% trans "BACK TO LIST" %}
+ + + From 641c556bb65602fc24dbb1fffdba68086aed0591 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 12 May 2019 21:16:46 +0200 Subject: [PATCH 027/626] Simplify code --- .../hosting/js/virtual_machine_detail.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 32ae3f53..5fb9f6e5 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -81,16 +81,7 @@ $(document).ready(function() { }) }); - $('#modal-add-ssh-key').on('hidden.bs.modal', function () { - location.reload(); - }); - - haveResponse = false; - $('#modal-add-ssh-key').on('click', '.btn-ok', function(e) { - if(haveResponse) { - console.log("We had response, so reload"); - location.reload(); - } + $('#modal-add-ssh-key-button').click(function(e) { var url = $('#add_ssh_key_to_vm_form').attr('action'); console.log("Url to POST " + url); @@ -106,7 +97,7 @@ $(document).ready(function() { console.log("Encoded data = " + encoded_data); fa_icon = $('#ssh-key-modal-icon'); - modal_btn = $('#ssh-key-modal-button'); + modal_btn = $('#modal-add-ssh-key-button'); modal_btn.prop("disabled", true); modal_btn.html(''); $.post(url, {selected_key: encoded_data}) @@ -115,7 +106,7 @@ $(document).ready(function() { modal_btn.prop("disabled", false); modal_btn.html("OK"); if (data.status === true) { - fa_icon.html(''); + fa_icon.html(''); } else { fa_icon.html(''); modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); @@ -136,7 +127,9 @@ $(document).ready(function() { }) .always(function () { console.log("changing href to location: " + location); - haveResponse = true; + $('#modal-add-ssh-key-button').unbind('click').click(function () { + location.reload(); + }); }) }); From 1faf46cc1b57a1e737d8ba694d3dff1e2ada3a36 Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Sun, 12 May 2019 21:34:10 -0400 Subject: [PATCH 028/626] added validation to heck if the user is the one allowed to access --- hosting/views.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 4c0d8b41..f39e1b58 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1,5 +1,7 @@ import logging import uuid +import os +import dotenv from datetime import datetime from time import sleep @@ -28,6 +30,7 @@ from django.views.generic import ( ) from rest_framework.views import APIView from rest_framework.response import Response +from rest_framework.renderers import JSONRenderer from guardian.mixins import PermissionRequiredMixin from oca.pool import WrongIdError from stored_messages.api import mark_read @@ -36,7 +39,7 @@ from stored_messages.settings import stored_messages_settings from datacenterlight.cms_models import DCLCalculatorPluginModel from datacenterlight.models import VMTemplate, VMPricing -from datacenterlight.utils import create_vm, get_cms_integration +from datacenterlight.utils import create_vm, get_cms_integration, check_otp, env from hosting.models import UserCardDetail from membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager @@ -68,9 +71,12 @@ from .models import ( logger = logging.getLogger(__name__) + CONNECTION_ERROR = "Your VMs cannot be displayed at the moment due to a \ backend connection error. please try again in a few \ minutes." + + decorators = [never_cache] @@ -1760,11 +1766,20 @@ def forbidden_view(request, exception=None, reason=''): class CheckUserVM(APIView): + renderer_classes = (JSONRenderer, ) def get(self, request): try: email = request.data['email'] ip = request.data['ip'] + user = request.data['user'] + realm = request.data['realm'] + token = request.data['token'] + if user != env('ACCOUNT_NAME'): + return Response("User not allowed", 403) + response = check_otp(user, realm, token) + if response != 200: + return Response('Invalid token', 403) uservms = VMDetail.objects.filter(user__email=email) if len(uservms) > 0: for i in range(len(uservms)): From fda5118c39029d27c369e4fc762b6736e3d7e1fb Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Sun, 12 May 2019 21:34:54 -0400 Subject: [PATCH 029/626] Added otp verification --- datacenterlight/utils.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index bbcb16ab..808a7643 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -1,4 +1,8 @@ import logging +import os +import pyotp +import requests +import dotenv from django.contrib.sites.models import Site from datacenterlight.tasks import create_vm_task @@ -11,6 +15,17 @@ from .models import VMPricing, VMTemplate logger = logging.getLogger(__name__) +PROJECT_DIR = os.path.abspath( + os.path.join(os.path.dirname(__file__)), +) + +# load .env file +dotenv.read_dotenv("{0}/.env".format(PROJECT_DIR)) + + +def env(env_name): + return os.environ.get(env_name) + def get_cms_integration(name): current_site = Site.objects.get_current() @@ -100,3 +115,22 @@ def clear_all_session_vars(request): 'generic_payment_details', 'product_id']: if session_var in request.session: del request.session[session_var] + + +def check_otp(name, realm, token): + data = { + "auth_name": env('AUTH_NAME'), + "auth_token": pyotp.TOTP(env('AUTH_SEED')).now(), + "auth_realm": env('AUTH_REALM'), + "name": name, + "realm": realm, + "token": token + } + response = requests.post( + "https://{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format( + OTP_SERVER=env('OTP_SERVER'), + OTP_VERIFY_ENDPOINT=env('OTP_VERIFY_ENDPOINT') + ), + data=data + ) + return response.status_code From 5e2e906f48d70c12244fda0557e4c1c1ce923df0 Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Sun, 12 May 2019 21:35:28 -0400 Subject: [PATCH 030/626] include pyotp in requeriments --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index fe70299b..22da4c26 100644 --- a/requirements.txt +++ b/requirements.txt @@ -98,3 +98,4 @@ amqp==2.2.1 vine==1.1.4 cdist==4.7.0 git+https://github.com/ungleich/djangocms-multisite.git#egg=djangocms_multisite +pyotp From 9fd396363f79aa594e74b487e9a709e56b4f8db8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 13 May 2019 07:13:49 +0200 Subject: [PATCH 031/626] Center the add ssh key nicely --- hosting/static/hosting/css/virtual-machine.css | 5 +++++ hosting/templates/hosting/virtual_machine_detail.html | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/hosting/static/hosting/css/virtual-machine.css b/hosting/static/hosting/css/virtual-machine.css index 726b0f35..abd459a0 100644 --- a/hosting/static/hosting/css/virtual-machine.css +++ b/hosting/static/hosting/css/virtual-machine.css @@ -199,6 +199,11 @@ /* text-align: center; */ } +.vm-add-ssh-key { + margin: 25px 0 30px; + /* text-align: center; */ +} + @media(min-width: 768px) { .vm-detail-contain { display: flex; diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index 1aad409a..edbc71f9 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -82,7 +82,7 @@ {% if keys|length > 0 %} {% if not virtual_machine.status == 'canceled' %} -
+
{% trans "Sorry, there was an unexpected error. Kindly retry." %} From caa01f344ff69f11a5b18e6356944a29386416dd Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 13 May 2019 07:56:46 +0200 Subject: [PATCH 032/626] Change poweroff to poweroff_hard Issue with poweroff: Executing poweroff not always seems to work. Sometimes, the VM is tries to SHUTDOWN but times out. poweroff-hard seems to poweroff all the time. --- opennebula_api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 7348d538..ef07d4ce 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -367,7 +367,7 @@ class OpenNebulaManager(): vm = None try: vm = self.get_vm(vm_id) - vm.poweroff() + vm.poweroff_hard() except socket.timeout as socket_err: logger.error("Socket timeout error: {0}".format(socket_err)) except OpenNebulaException as opennebula_err: From 72ea362d0143f28f1e2ebea0f575d5608f74c1a7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 13 May 2019 08:01:11 +0200 Subject: [PATCH 033/626] Remove duplicated blocktrans in txt email template --- hosting/templates/hosting/emails/ssh_key_added_to_vm.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt b/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt index ede56e3a..665936cb 100644 --- a/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt +++ b/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt @@ -1,6 +1,6 @@ {% load i18n %} -{% blocktrans %}{% blocktrans %}SSH key(s) {{keys}} added to your VM {{vm_name}}{% endblocktrans %} +{% blocktrans %}SSH key(s) {{keys}} added to your VM {{vm_name}}{% endblocktrans %} {% blocktrans %}You initiated adding the keys {{keys}} to your VM {{ vm_name }}!{% endblocktrans %} {% blocktrans %}This email is to notify you that it was accomplished successfully.{% endblocktrans %} From 69ec7d2b4627a37e555a29e087d6803c49f37050 Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Mon, 13 May 2019 03:44:09 -0400 Subject: [PATCH 034/626] reuse of the env variable in the base settings --- datacenterlight/utils.py | 25 ++++++------------------- dynamicweb/settings/base.py | 8 ++++++++ hosting/views.py | 7 +++---- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 808a7643..208d39f3 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -1,9 +1,8 @@ import logging -import os import pyotp import requests -import dotenv from django.contrib.sites.models import Site +from django.conf import settings from datacenterlight.tasks import create_vm_task from hosting.models import HostingOrder, HostingBill, OrderDetail @@ -15,18 +14,6 @@ from .models import VMPricing, VMTemplate logger = logging.getLogger(__name__) -PROJECT_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__)), -) - -# load .env file -dotenv.read_dotenv("{0}/.env".format(PROJECT_DIR)) - - -def env(env_name): - return os.environ.get(env_name) - - def get_cms_integration(name): current_site = Site.objects.get_current() try: @@ -119,17 +106,17 @@ def clear_all_session_vars(request): def check_otp(name, realm, token): data = { - "auth_name": env('AUTH_NAME'), - "auth_token": pyotp.TOTP(env('AUTH_SEED')).now(), - "auth_realm": env('AUTH_REALM'), + "auth_name": settings.AUTH_NAME, + "auth_token": pyotp.TOTP(settings.AUTH_SEED).now(), + "auth_realm": settings.AUTH_REALM, "name": name, "realm": realm, "token": token } response = requests.post( "https://{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format( - OTP_SERVER=env('OTP_SERVER'), - OTP_VERIFY_ENDPOINT=env('OTP_VERIFY_ENDPOINT') + OTP_SERVER=settings.OTP_SERVER, + OTP_VERIFY_ENDPOINT=settings.OTP_VERIFY_ENDPOINT ), data=data ) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index b267c31d..27909813 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -721,6 +721,14 @@ X_FRAME_OPTIONS = ('SAMEORIGIN' if X_FRAME_OPTIONS_ALLOW_FROM_URI is None else DEBUG = bool_env('DEBUG') +ACCOUNT_NAME = env('ACCOUNT_NAME') +AUTH_NAME = env('AUTH_NAME') +AUTH_SEED = env('AUTH_SEED') +AUTH_REALM = env('AUTH_REALM') +OTP_SERVER = env('OTP_SERVER') +OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT') + + if DEBUG: from .local import * # flake8: noqa else: diff --git a/hosting/views.py b/hosting/views.py index f39e1b58..b0cee45c 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1,7 +1,5 @@ import logging import uuid -import os -import dotenv from datetime import datetime from time import sleep @@ -39,7 +37,7 @@ from stored_messages.settings import stored_messages_settings from datacenterlight.cms_models import DCLCalculatorPluginModel from datacenterlight.models import VMTemplate, VMPricing -from datacenterlight.utils import create_vm, get_cms_integration, check_otp, env +from datacenterlight.utils import create_vm, get_cms_integration, check_otp from hosting.models import UserCardDetail from membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager @@ -1775,7 +1773,8 @@ class CheckUserVM(APIView): user = request.data['user'] realm = request.data['realm'] token = request.data['token'] - if user != env('ACCOUNT_NAME'): + print(settings.ACCOUNT_NAME) + if user != settings.ACCOUNT_NAME: return Response("User not allowed", 403) response = check_otp(user, realm, token) if response != 200: From 94d5c34152055be753acb38cefbd97cb0e051d56 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 13 May 2019 21:15:38 +0200 Subject: [PATCH 035/626] [hosting/bill] Skip creating MHB for invoices that have been imported already --- hosting/management/commands/fetch_stripe_bills.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hosting/management/commands/fetch_stripe_bills.py b/hosting/management/commands/fetch_stripe_bills.py index df30535c..20f1cbe0 100644 --- a/hosting/management/commands/fetch_stripe_bills.py +++ b/hosting/management/commands/fetch_stripe_bills.py @@ -45,7 +45,12 @@ class Command(BaseCommand): num_invoice_created = 0 for invoice in all_invoices: invoice['customer'] = user.stripecustomer - num_invoice_created += 1 if MonthlyHostingBill.create(invoice) is not None else logger.error("Did not import invoice for %s" % str(invoice)) + try: + existing_mhb = MonthlyHostingBill.objects.get(invoice_id=invoice['invoice_id']) + 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)) self.stdout.write( self.style.SUCCESS("Number of invoices imported = %s" % num_invoice_created) ) From ce630573e027fdea9a6cad8c31bf6620e2b5bc89 Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Thu, 16 May 2019 13:33:31 -0400 Subject: [PATCH 036/626] Remove print statement & correct code return --- hosting/views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index b0cee45c..88adaf22 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1773,7 +1773,6 @@ class CheckUserVM(APIView): user = request.data['user'] realm = request.data['realm'] token = request.data['token'] - print(settings.ACCOUNT_NAME) if user != settings.ACCOUNT_NAME: return Response("User not allowed", 403) response = check_otp(user, realm, token) @@ -1784,8 +1783,8 @@ class CheckUserVM(APIView): for i in range(len(uservms)): if uservms[i].ipv4 == ip or uservms[i].ipv6 == ip: return Response('success', 200) - return Response('No VM found matching the ip address provided', 403) + return Response('No VM found matching the ip address provided', 404) else: - return Response('No VM found with the given email address', 403) + return Response('No VM found with the given email address', 404) except KeyError: return Response('Not enough data provided', 400) From a82ecbe4d50d7bd3e7fd0497b930ad4844f58309 Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Thu, 16 May 2019 13:34:13 -0400 Subject: [PATCH 037/626] fix typho in check_vm --- hosting/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/urls.py b/hosting/urls.py index 1d0033ef..2c8ff8ab 100644 --- a/hosting/urls.py +++ b/hosting/urls.py @@ -16,7 +16,7 @@ from .views import ( urlpatterns = [ url(r'index/?$', IndexView.as_view(), name='index'), url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'), - url(r'checkvm/?$', CheckUserVM.as_view(), name='chech_vm'), + url(r'checkvm/?$', CheckUserVM.as_view(), name='check_vm'), url(r'dashboard/?$', DashboardView.as_view(), name='dashboard'), url(r'nodejs/?$', NodeJSHostingView.as_view(), name='nodejshosting'), url(r'rails/?$', RailsHostingView.as_view(), name='railshosting'), From 0cada8668a135253a5220b8b73c4ccb1f0dcda43 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 16 May 2019 22:10:41 +0200 Subject: [PATCH 038/626] Update Changelog for 2.5.10 --- Changelog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changelog b/Changelog index ec77e55b..747d4028 100644 --- a/Changelog +++ b/Changelog @@ -1,4 +1,5 @@ -Next: +2.5.10: 2019-05-16 + * #6672: [api] REST endpoint for ungleich-cli to verify if a VM belongs to a user (MR!705) * #6670: [hosting/save_ssh_key] Upgrade cdist version to 5.0.1 to manage keys on Alpine linux 2.5.9: 2019-05-09 * #6669: [hosting] Fix opennebula vm query takes long (MR!703) From 5ad871f1248027fba718ff0e5fda0a3dcd247751 Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Thu, 16 May 2019 16:35:44 -0400 Subject: [PATCH 039/626] updated for read vm realm --- dynamicweb/settings/base.py | 2 +- hosting/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 27909813..1051c4ab 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -721,7 +721,7 @@ X_FRAME_OPTIONS = ('SAMEORIGIN' if X_FRAME_OPTIONS_ALLOW_FROM_URI is None else DEBUG = bool_env('DEBUG') -ACCOUNT_NAME = env('ACCOUNT_NAME') +READ_VM_REALM = env('READ_VM_REALM') AUTH_NAME = env('AUTH_NAME') AUTH_SEED = env('AUTH_SEED') AUTH_REALM = env('AUTH_REALM') diff --git a/hosting/views.py b/hosting/views.py index 88adaf22..cd6aa4f3 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1773,7 +1773,7 @@ class CheckUserVM(APIView): user = request.data['user'] realm = request.data['realm'] token = request.data['token'] - if user != settings.ACCOUNT_NAME: + if realm != settings.READ_VM_REALM: return Response("User not allowed", 403) response = check_otp(user, realm, token) if response != 200: From 63a78a537e1300e393f6fcf54aae799210618264 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 8 Jun 2019 04:25:55 +0200 Subject: [PATCH 040/626] Use infoextended for fallback case also --- opennebula_api/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index a333aa23..a951349e 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -212,7 +212,13 @@ class OpenNebulaManager(): 'Could not connect via client, using oneadmin instead') try: vm_pool = oca.VirtualMachinePool(self.oneadmin_client) - vm_pool.info(filter=-2) + if infoextended: + vm_pool.infoextended( + filter=-1, # User's resources and any of his groups + vm_state=-1 # Look for VMs in any state, except DONE + ) + else: + vm_pool.info(filter=-2) return vm_pool except: raise ConnectionRefusedError From 496178f44cbfbfc585cc8c0ddd918c9b2ff98e08 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 8 Jun 2019 04:40:16 +0200 Subject: [PATCH 041/626] Check if VM belongs to user against opennebula backend --- hosting/views.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index cd6aa4f3..11a4b8bc 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -26,11 +26,11 @@ from django.views.generic import ( View, CreateView, FormView, ListView, DetailView, DeleteView, TemplateView, UpdateView ) -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework.renderers import JSONRenderer from guardian.mixins import PermissionRequiredMixin from oca.pool import WrongIdError +from rest_framework.renderers import JSONRenderer +from rest_framework.response import Response +from rest_framework.views import APIView from stored_messages.api import mark_read from stored_messages.models import Message from stored_messages.settings import stored_messages_settings @@ -1778,13 +1778,23 @@ class CheckUserVM(APIView): response = check_otp(user, realm, token) if response != 200: return Response('Invalid token', 403) - uservms = VMDetail.objects.filter(user__email=email) - if len(uservms) > 0: - for i in range(len(uservms)): - if uservms[i].ipv4 == ip or uservms[i].ipv6 == ip: - return Response('success', 200) - return Response('No VM found matching the ip address provided', 404) - else: - return Response('No VM found with the given email address', 404) + manager = OpenNebulaManager() + # not the best way to lookup vms by ip + # TODO: make this optimal + vms = manager.get_vms() + users_vms = [VirtualMachineSerializer(vm).data for vm in vms + if vm.uname == email] + if len(users_vms) == 0: + return Response('No VM found with the given email address', + 404) + for vm in users_vms: + for nic in vm.template.nics: + if hasattr(nic, 'ip6_global'): + if nic.ip6_global == ip: + return Response('success', 200) + elif hasattr(nic, 'ip'): + if nic.ip == ip: + return Response('success', 200) + return Response('No VM found matching the ip address provided', 404) except KeyError: return Response('Not enough data provided', 400) From c99e943ebcec10140b89453f225167beb16c334f Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 10 Jun 2019 09:23:48 +0200 Subject: [PATCH 042/626] Use oneadmin_client to update a user's ssh key --- opennebula_api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index ef07d4ce..761c84b7 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -432,7 +432,7 @@ class OpenNebulaManager(): :return: """ - return_value = self.client.call( + return_value = self.oneadmin_client.call( 'user.update', self.opennebula_user.id, '%s' % ssh_key, From 1ebfc8b2dcf392958e14cde0075946ad231a889d Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 10 Jun 2019 14:51:40 +0200 Subject: [PATCH 043/626] Don't use VirtualMachineSerializer for obtaining users_vms --- hosting/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 11a4b8bc..4c6ea04d 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1782,8 +1782,7 @@ class CheckUserVM(APIView): # not the best way to lookup vms by ip # TODO: make this optimal vms = manager.get_vms() - users_vms = [VirtualMachineSerializer(vm).data for vm in vms - if vm.uname == email] + users_vms = [vm for vm in vms if vm.uname == email] if len(users_vms) == 0: return Response('No VM found with the given email address', 404) From e5f317281f869c9cc030db95d27cddbab389380d Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Mon, 10 Jun 2019 18:25:18 -0400 Subject: [PATCH 044/626] fix translation Learn more -> Lerne mehr --- digitalglarus/locale/de/LC_MESSAGES/django.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/digitalglarus/locale/de/LC_MESSAGES/django.po b/digitalglarus/locale/de/LC_MESSAGES/django.po index 2d0129c4..ec96f5dc 100644 --- a/digitalglarus/locale/de/LC_MESSAGES/django.po +++ b/digitalglarus/locale/de/LC_MESSAGES/django.po @@ -342,7 +342,7 @@ msgstr "" "dieser Website erklärst Du Dich damit einverstanden, diese zu nutzen." msgid "Learn more" -msgstr "Learn mehr" +msgstr "Lerne mehr" msgid "OK" msgstr "" From 151983ff592d03145fc9d6870a5862079c301136 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 11 Jun 2019 01:09:45 +0200 Subject: [PATCH 045/626] Update Changelog for 2.5.11 --- Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog b/Changelog index 747d4028..cfefdd01 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,6 @@ +2.5.11: 2019-06-11 + * #6672: [api] Check VM belongs to user in the infrastructure directly (MR!707) + * #bugfix: DE translation fix "Learn mehr" -> "Lerne mehr" (MR!708) 2.5.10: 2019-05-16 * #6672: [api] REST endpoint for ungleich-cli to verify if a VM belongs to a user (MR!705) * #6670: [hosting/save_ssh_key] Upgrade cdist version to 5.0.1 to manage keys on Alpine linux From 108fbb09b00bf15a77201aa162506d9f326bfa16 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 04:29:34 +0200 Subject: [PATCH 046/626] Add ssh key form to order_detail page To ask for the SSH key at the time of confirming and placing the order. The order does not proceed until the user provides a valid ssh key. --- .../datacenterlight/order_detail.html | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 31933e12..fa2ba7ca 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -134,6 +134,27 @@
{% csrf_token %} + {% if generic_payment_details %} + {% else %} + {% comment %} + We are in VM buy flow and we want user to click the "Place order" button. + At this point, we also want the user to input the SSH key for the VM. + {% endcomment %} + + {% if messages %} +
+ {% for message in messages %} + {{ message }} + {% endfor %} +
+ {% endif %} +
+

 {% trans "Add your public SSH key" %}

+
+ {% for field in form %} + {% bootstrap_field field %} + {% endfor %} + {% endif %}
{% if generic_payment_details %} From 1e68ecb047076bbcaf461e8646120c565d7c979b Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 04:31:29 +0200 Subject: [PATCH 047/626] Confirm order button close: Redirect only to url specified --- hosting/static/hosting/js/virtual_machine_detail.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 5fb9f6e5..e90e73f6 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -161,8 +161,11 @@ $(document).ready(function() { modal_btn = $('#createvm-modal-done-btn'); $('#createvm-modal-title').text(data.msg_title); $('#createvm-modal-body').html(data.msg_body); - modal_btn.attr('href', data.redirect) - .removeClass('hide'); + if (data.redirect) { + modal_btn.attr('href', data.redirect).removeClass('hide'); + } else { + modal_btn.attr('href', ""); + } if (data.status === true) { fa_icon.attr('class', 'checkmark'); } else { From 87f5bf3dcc61abcc0ae00e0a7aff1aca5ee55007 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 04:32:27 +0200 Subject: [PATCH 048/626] Pass UserHostingKeyForm to the context of OrderConfirmationView --- datacenterlight/views.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 5dc3a3d3..f01766d1 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -13,7 +13,8 @@ from django.views.decorators.cache import cache_control from django.views.generic import FormView, CreateView, DetailView from hosting.forms import ( - HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm + HostingUserLoginForm, GenericPaymentForm, ProductPaymentForm, + UserHostingKeyForm ) from hosting.models import ( HostingBill, HostingOrder, UserCardDetail, GenericProduct @@ -529,12 +530,18 @@ class PaymentOrderView(FormView): return self.render_to_response(context) -class OrderConfirmationView(DetailView): +class OrderConfirmationView(DetailView, FormView): + form_class = UserHostingKeyForm template_name = "datacenterlight/order_detail.html" payment_template_name = 'datacenterlight/landing_payment.html' context_object_name = "order" model = HostingOrder + def get_form_kwargs(self): + kwargs = super(OrderConfirmationView, self).get_form_kwargs() + kwargs.update({'request': self.request}) + return kwargs + @cache_control(no_cache=True, must_revalidate=True, no_store=True) def get(self, request, *args, **kwargs): context = {} @@ -567,6 +574,7 @@ class OrderConfirmationView(DetailView): else: context.update({ 'vm': request.session.get('specs'), + 'form': UserHostingKeyForm(request=self.request), }) context.update({ 'site_url': reverse('datacenterlight:index'), @@ -583,6 +591,24 @@ class OrderConfirmationView(DetailView): stripe_api_cus_id = request.session.get('customer') stripe_utils = StripeUtils() + # Check ssh public key and then proceed + form = self.get_form() + required = 'add_ssh' in self.request.POST + form.fields['name'].required = required + form.fields['public_key'].required = required + if not form.is_valid(): + response = { + 'status': False, + 'msg_title': str(_('SSH key related error occurred')), + 'msg_body': "
".join([str(v) for k,v in form.errors.items()]), + } + return JsonResponse(response) + + # We have a valid SSH key from the user, save it in opennebula and db + # and proceed further + form.save() + user_public_key = form.data["public_key"] + if 'token' in request.session: card_details = stripe_utils.get_cards_details_from_token( request.session.get('token') From 110f29171da1d1aa8b270c75fdae4cc64f94d77c Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 04:33:48 +0200 Subject: [PATCH 049/626] Update user ssh key in opennebula --- datacenterlight/tasks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 0ce71577..641bcf6b 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -81,6 +81,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): custom_user = CustomUser.objects.get(email=user.get('email')) pub_keys = get_all_public_keys(custom_user) + if manager.email != settings.OPENNEBULA_USERNAME: + manager.save_key_in_opennebula_user('\n'.join(pub_keys)) vm_id = manager.create_vm( template_id=vm_template_id, specs=specs, From ecc26d14e5d6d988a613ca70f98bfa9ce63302d1 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 17:24:35 +0200 Subject: [PATCH 050/626] Show previous keys if exist in order confirmation --- .../templates/datacenterlight/order_detail.html | 9 +++++++++ datacenterlight/views.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index fa2ba7ca..1f01c201 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -151,6 +151,15 @@

 {% trans "Add your public SSH key" %}

+
+ {% if keys|length > 0 %} +
Previous keys
+ {% endif %} + {% for key in keys %} + +
+ {% endfor %} + {% for field in form %} {% bootstrap_field field %} {% endfor %} diff --git a/datacenterlight/views.py b/datacenterlight/views.py index f01766d1..7aa6d8a4 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -25,7 +25,7 @@ from utils.forms import ( BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm, BillingAddress ) -from utils.hosting_utils import get_vm_price_with_vat +from utils.hosting_utils import get_vm_price_with_vat, get_all_public_keys from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task from .cms_models import DCLCalculatorPluginModel @@ -575,6 +575,7 @@ class OrderConfirmationView(DetailView, FormView): context.update({ 'vm': request.session.get('specs'), 'form': UserHostingKeyForm(request=self.request), + 'keys': get_all_public_keys(self.request.user) }) context.update({ 'site_url': reverse('datacenterlight:index'), @@ -607,7 +608,6 @@ class OrderConfirmationView(DetailView, FormView): # We have a valid SSH key from the user, save it in opennebula and db # and proceed further form.save() - user_public_key = form.data["public_key"] if 'token' in request.session: card_details = stripe_utils.get_cards_details_from_token( From 04f1112b098caadbdbcaeee72b7deddad2fe8d8b Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 18:26:45 +0200 Subject: [PATCH 051/626] Add key in the text area --- datacenterlight/templates/datacenterlight/order_detail.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 1f01c201..85f87168 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -156,7 +156,9 @@
Previous keys
{% endif %} {% for key in keys %} - +
{% endfor %} From ba7ff9e409e484881f0cf3b909c601dff4d74f78 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 18:39:25 +0200 Subject: [PATCH 052/626] Adjust textarea styles --- datacenterlight/templates/datacenterlight/order_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 85f87168..410ac2ff 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -156,7 +156,7 @@
Previous keys
{% endif %} {% for key in keys %} -
From a330dee9a1e61e09e8cbdba42f13c74318938537 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 24 Jun 2019 18:58:44 +0200 Subject: [PATCH 053/626] Modify style --- datacenterlight/static/datacenterlight/css/common.css | 5 +++++ datacenterlight/templates/datacenterlight/order_detail.html | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/datacenterlight/static/datacenterlight/css/common.css b/datacenterlight/static/datacenterlight/css/common.css index 00ee52cc..b19b5852 100644 --- a/datacenterlight/static/datacenterlight/css/common.css +++ b/datacenterlight/static/datacenterlight/css/common.css @@ -186,3 +186,8 @@ footer .dcl-link-separator::before { background: transparent !important; resize: none; } + +.existing-keys-title { + font-weight: bold; + font-size: 14px; +} diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 410ac2ff..bebb4d45 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -151,9 +151,9 @@

 {% trans "Add your public SSH key" %}

-
+
{% if keys|length > 0 %} -
Previous keys
+
Existing keys
{% endif %} {% for key in keys %}
{% endfor %} - +
{% for field in form %} {% bootstrap_field field %} {% endfor %} From 08608c726fb20b84b76aa93a0f7793676da2884f Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 25 Jun 2019 02:11:57 +0200 Subject: [PATCH 055/626] Code cleanup: remove updating ssh keys on live VMs --- datacenterlight/tasks.py | 87 +------------------ .../hosting/js/virtual_machine_detail.js | 52 ----------- .../hosting/virtual_machine_detail.html | 35 -------- hosting/urls.py | 4 +- hosting/views.py | 55 ------------ 5 files changed, 4 insertions(+), 229 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 641bcf6b..8b4626e8 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -1,5 +1,4 @@ from datetime import datetime -from time import sleep from celery import current_task from celery.exceptions import MaxRetriesExceededError @@ -178,7 +177,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): kwargs={'pk': order_id}), 'page_header': _( 'Your New VM %(vm_name)s at Data Center Light') % { - 'vm_name': vm.get('name')}, + 'vm_name': vm.get('name')}, 'vm_name': vm.get('name') } email_data = { @@ -193,7 +192,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): email.send() logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) - get_or_create_vm_detail(custom_user, manager, vm_id) + if vm_id > 0: + get_or_create_vm_detail(custom_user, manager, vm_id) except Exception as e: logger.error(str(e)) try: @@ -215,84 +215,3 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): return return vm_id - - -@app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES) -def save_ssh_key_in_vm_template_task(self, user, vm_id, ssh_key_str): - logger.debug("Inside save_ssh_key_in_vm_template_task %s" % vm_id) - - on_user = user.get('email') - on_pass = user.get('pass') - - if on_user is None or on_pass is None: - logger.error( - "Either email or password not supplied. Can't save ssh key" - ) - return - - manager = OpenNebulaManager(email=on_user, password=on_pass) - - # poweroff the vm - vm = manager.power_off_vm(vm_id) - - powered_off = False - for t in range(15): - vm = manager.get_vm(vm_id) - if vm.str_state == 'POWEROFF': - logger.debug( - "VM %s has been powered off. Now adding ssh keys" % vm.id - ) - powered_off = True - break - else: - logger.debug( - "VM {} has state {}. Waiting 2 more seconds to see if it " - "powers off".format(vm.id, vm.str_state) - ) - sleep(2) - - if powered_off: - logger.debug( - "VM %s was powered off by api call" % vm.id - ) - if manager.save_key_in_vm_template(vm_id=vm_id, ssh_key=ssh_key_str) > 0: - logger.debug( - "Added ssh_keys of user %s to VM %s successfully" % - (on_user, vm_id) - ) - manager.resume(vm_id) - lang = 'en-us' - if user.get('language') is not None: - logger.debug( - "Language is set to {}".format(user.get('language'))) - lang = user.get('language') - translation.activate(lang) - # Send notification to the user as soon as VM has been booked - context = { - 'page_header': str(_("Adding of SSH key completed")), - 'base_url': "{0}://{1}".format(user.get('request_scheme'), - user.get('request_host')), - 'vm_detail_url': reverse('hosting:virtual_machines', - kwargs={'pk': vm_id}), - 'vm_name': vm.name - } - email_data = { - 'subject': context.get('page_header'), - 'to': user.get('email'), - 'context': context, - 'template_name': 'ssh_key_added_to_vm', - 'template_path': 'hosting/emails/', - 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, - } - email = BaseEmail(**email_data) - email.send() - else: - logger.error( - "There was an error updating ssh keys of the VM %s" % vm_id - ) - else: - logger.error( - "VM {} did not poweroff within 30 seconds after the poweroff api " - "call. Please, ask the admin to poweroff and add the key " - "manually.".format(vm_id) - ) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index e90e73f6..28592883 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -81,58 +81,6 @@ $(document).ready(function() { }) }); - $('#modal-add-ssh-key-button').click(function(e) { - var url = $('#add_ssh_key_to_vm_form').attr('action'); - console.log("Url to POST " + url); - - // Declare a checkbox array - var chkArray = []; - var encoded_data =""; - - // Look for all checkboxes that have a specific class and was checked - $(".chk-ssh-key:checked").each(function() { - chkArray.push($(this).val()); - }); - encoded_data = encodeURIComponent(chkArray.join(",")); - console.log("Encoded data = " + encoded_data); - - fa_icon = $('#ssh-key-modal-icon'); - modal_btn = $('#modal-add-ssh-key-button'); - modal_btn.prop("disabled", true); - modal_btn.html(''); - $.post(url, {selected_key: encoded_data}) - .done(function(data) { - console.log("Request Done"); - modal_btn.prop("disabled", false); - modal_btn.html("OK"); - if (data.status === true) { - fa_icon.html(''); - } else { - fa_icon.html(''); - modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); - } - console.log("title = " + data.msg_title); - console.log("desc = " + data.msg_body); - $('#ssh-key-modal-title').text(data.msg_title); - $('#ssh-key-modal-description').html(data.msg_body); - console.log("Request Done end"); - }) - .fail(function(data) { - console.log("Request Failed"); - console.log("title " + data.msg_title); - console.log("body " + data.msg_body); - modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); - $('#ssh-key-modal-title').text(data.msg_title); - $('#ssh-key-modal-description').html(data.msg_body); - }) - .always(function () { - console.log("changing href to location: " + location); - $('#modal-add-ssh-key-button').unbind('click').click(function () { - location.reload(); - }); - }) - }); - var hash = window.location.hash; hash && $('ul.nav a[href="' + hash + '"]').tab('show'); diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index edbc71f9..bf869f91 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -113,41 +113,6 @@ {% trans "BACK TO LIST" %}
- - -
- {% if keys|length > 0 %} - {% if not virtual_machine.status == 'canceled' %} -
- -
- {% trans "Sorry, there was an unexpected error. Kindly retry." %} -
-
- {% endif %} - {% else %} - - {% endif %} -

{% trans "Support / Contact" %}

diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 761c84b7..478758fc 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -363,64 +363,6 @@ class OpenNebulaManager(): return vm_terminated - def power_off_vm(self, vm_id): - vm = None - try: - vm = self.get_vm(vm_id) - vm.poweroff_hard() - except socket.timeout as socket_err: - logger.error("Socket timeout error: {0}".format(socket_err)) - except OpenNebulaException as opennebula_err: - logger.error( - "OpenNebulaException error: {0}".format(opennebula_err)) - except OSError as os_err: - logger.error("OSError : {0}".format(os_err)) - except ValueError as value_err: - logger.error("ValueError : {0}".format(value_err)) - - return vm - - def resume(self, vm_id): - vm = None - try: - vm = self.get_vm(vm_id) - vm.resume() - except socket.timeout as socket_err: - logger.error("Socket timeout error: {0}".format(socket_err)) - except OpenNebulaException as opennebula_err: - logger.error( - "OpenNebulaException error: {0}".format(opennebula_err)) - except OSError as os_err: - logger.error("OSError : {0}".format(os_err)) - except ValueError as value_err: - logger.error("ValueError : {0}".format(value_err)) - - return vm - - def save_key_in_vm_template(self, vm_id, ssh_key): - """ - Update the template of a given VM and set the ssh key of the user - :param vm_id: the identifier of the VM object - :param ssh_key: a newline(\n) separated ssh key string that needs to be - set in the VM template - :return: - """ - UPDATE_TYPE = 1 - return_value = self.client.call( - 'vm.updateconf', - vm_id, - '%s' % ssh_key, - UPDATE_TYPE - ) - if type(return_value) == int: - logger.debug( - "Saved the key in VM Template success : %s" % return_value) - else: - logger.error( - "Could not save the key in VM Template. %s" % return_value) - - return return_value - def save_key_in_opennebula_user(self, ssh_key, update_type=1): """ Save the given ssh key in OpenNebula user From 9ee1b7c124dd3cd4367198b07ae0939caed91691 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 25 Jun 2019 02:25:17 +0200 Subject: [PATCH 057/626] Make public_key form params mandatory only if existing keys do not exist --- datacenterlight/views.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 7aa6d8a4..914c66eb 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -595,8 +595,12 @@ class OrderConfirmationView(DetailView, FormView): # Check ssh public key and then proceed form = self.get_form() required = 'add_ssh' in self.request.POST - form.fields['name'].required = required - form.fields['public_key'].required = required + + # SSH key is required only if the user doesn't have an existing + # key + if len(get_all_public_keys(self.request.user)) == 0: + form.fields['name'].required = required + form.fields['public_key'].required = required if not form.is_valid(): response = { 'status': False, From 85f7d734424a24dfcd272c36d59f2d6432bdb9dd Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 25 Jun 2019 02:32:19 +0200 Subject: [PATCH 058/626] Code cleanup: Remove ssh_key_added_to_vm email templates --- .../hosting/emails/ssh_key_added_to_vm.html | 51 ------------------- .../hosting/emails/ssh_key_added_to_vm.txt | 11 ---- 2 files changed, 62 deletions(-) delete mode 100644 hosting/templates/hosting/emails/ssh_key_added_to_vm.html delete mode 100644 hosting/templates/hosting/emails/ssh_key_added_to_vm.txt diff --git a/hosting/templates/hosting/emails/ssh_key_added_to_vm.html b/hosting/templates/hosting/emails/ssh_key_added_to_vm.html deleted file mode 100644 index 086c3479..00000000 --- a/hosting/templates/hosting/emails/ssh_key_added_to_vm.html +++ /dev/null @@ -1,51 +0,0 @@ -{% load static i18n %} - - - - - - - {% blocktrans %}SSH key(s) {{keys}} added to your VM {{vm_name}}{% endblocktrans %} - - - - - - - - - - - - - - - - - - - - - -
- -
-

{% blocktrans %}SSH keys on your VM {{ vm_name }}{% endblocktrans %}

-
-

- {% blocktrans %}You initiated adding the keys {{keys}} to your VM {{ vm_name }}!{% endblocktrans %} -

-

- {% blocktrans %}This email is to notify you that it was accomplished successfully.{% endblocktrans %} -

-

- {% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %} -

-
- {% trans "View Detail" %} -
-

{% trans "Your Data Center Light Team" %}

-
- - - \ No newline at end of file diff --git a/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt b/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt deleted file mode 100644 index 665936cb..00000000 --- a/hosting/templates/hosting/emails/ssh_key_added_to_vm.txt +++ /dev/null @@ -1,11 +0,0 @@ -{% load i18n %} - -{% blocktrans %}SSH key(s) {{keys}} added to your VM {{vm_name}}{% endblocktrans %} - -{% blocktrans %}You initiated adding the keys {{keys}} to your VM {{ vm_name }}!{% endblocktrans %} -{% blocktrans %}This email is to notify you that it was accomplished successfully.{% endblocktrans %} -{% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %} - -{{ base_url }}{{ vm_detail_url }} - -{% trans "Your Data Center Light Team" %} \ No newline at end of file From 34c917acc266edcf8037c238383ad9f75d31e306 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 25 Jun 2019 03:10:50 +0200 Subject: [PATCH 059/626] Add SSH form to hosting VM buy flow also --- hosting/templates/hosting/order_detail.html | 29 ++++++++++++++++++ hosting/views.py | 33 +++++++++++++++++++-- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index 4a62e9fa..7ab79378 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -198,6 +198,35 @@ {% block submit_btn %}
{% csrf_token %} + {% comment %} + We are in VM buy flow and we want user to click the "Place order" button. + At this point, we also want the user to input the SSH key for the VM. + {% endcomment %} + + {% if messages %} +
+ {% for message in messages %} + {{ message }} + {% endfor %} +
+ {% endif %} +
+

 {% trans "Add your public SSH key" %}

+
+
+ {% if keys|length > 0 %} +
Existing keys
+ {% endif %} + {% for key in keys %} + +
+ {% endfor %} +
+ {% for field in form %} + {% bootstrap_field field %} + {% endfor %}
{% blocktrans with vm_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{ vm_price }} CHF/month{% endblocktrans %}.
diff --git a/hosting/views.py b/hosting/views.py index 2ef5383d..33a8748e 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -838,13 +838,19 @@ class PaymentVMView(LoginRequiredMixin, FormView): return self.form_invalid(form) -class OrdersHostingDetailView(LoginRequiredMixin, DetailView): +class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): + form_class = UserHostingKeyForm template_name = "hosting/order_detail.html" context_object_name = "order" login_url = reverse_lazy('hosting:login') permission_required = ['view_hostingorder'] model = HostingOrder + def get_form_kwargs(self): + kwargs = super(OrdersHostingDetailView, self).get_form_kwargs() + kwargs.update({'request': self.request}) + return kwargs + def get_object(self, queryset=None): order_id = self.kwargs.get('pk') try: @@ -869,6 +875,8 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): if self.request.GET.get('page') == 'payment': context['page_header_text'] = _('Confirm Order') + context['form'] = UserHostingKeyForm(request=self.request) + context['keys'] = get_all_public_keys(self.request.user) else: context['page_header_text'] = _('Invoice') if not self.request.user.has_perm( @@ -994,6 +1002,27 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView): @method_decorator(decorators) def post(self, request): + # Check ssh public key and then proceed + form = self.get_form() + required = 'add_ssh' in self.request.POST + + # SSH key is required only if the user doesn't have an existing + # key + if len(get_all_public_keys(self.request.user)) == 0: + form.fields['name'].required = required + form.fields['public_key'].required = required + if not form.is_valid(): + response = { + 'status': False, + 'msg_title': str(_('SSH key related error occurred')), + 'msg_body': "
".join([str(v) for k,v in form.errors.items()]), + } + return JsonResponse(response) + + # We have a valid SSH key from the user, save it in opennebula and db + # and proceed further + form.save() + template = request.session.get('template') specs = request.session.get('specs') stripe_utils = StripeUtils() @@ -1641,7 +1670,7 @@ class VirtualMachineView(LoginRequiredMixin, View): "manager.delete_vm returned False. Hence, error making " "xml-rpc call to delete vm failed." ) - response['text'] = str(_('Error terminating VM ')) + str(vm.id) + response['text'] = str(_('Error terminating VM')) + str(vm.id) else: for t in range(15): try: From feeb102f9265afd722960d27efdfe93eaf81186d Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 25 Jun 2019 03:48:29 +0200 Subject: [PATCH 060/626] Do SSH key validation only if the user doesn't have an existing key and the user has input some value in the add ssh key field --- datacenterlight/views.py | 30 +++++++++++++++++------------- hosting/forms.py | 3 ++- hosting/views.py | 22 +++++++++++++--------- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 914c66eb..76f50aec 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -588,19 +588,18 @@ class OrderConfirmationView(DetailView, FormView): return render(request, self.template_name, context) def post(self, request, *args, **kwargs): - user = request.session.get('user') - stripe_api_cus_id = request.session.get('customer') - stripe_utils = StripeUtils() - # Check ssh public key and then proceed form = self.get_form() - required = 'add_ssh' in self.request.POST + required = True - # SSH key is required only if the user doesn't have an existing - # key - if len(get_all_public_keys(self.request.user)) == 0: - form.fields['name'].required = required - form.fields['public_key'].required = required + # SSH key validation is required only if the user doesn't have an + # existing key and user has input some value in the add ssh key fields + if (len(get_all_public_keys(self.request.user)) > 0 and + (len(form.data.get('public_key')) == 0 and + len(form.data.get('name')) == 0)): + required = False + form.fields['name'].required = required + form.fields['public_key'].required = required if not form.is_valid(): response = { 'status': False, @@ -609,9 +608,14 @@ class OrderConfirmationView(DetailView, FormView): } return JsonResponse(response) - # We have a valid SSH key from the user, save it in opennebula and db - # and proceed further - form.save() + if required: + # We have a valid SSH key from the user, save it in opennebula and + # db and proceed further + form.save() + + user = request.session.get('user') + stripe_api_cus_id = request.session.get('customer') + stripe_utils = StripeUtils() if 'token' in request.session: card_details = stripe_utils.get_cards_details_from_token( diff --git a/hosting/forms.py b/hosting/forms.py index 576a1996..797bc700 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -187,7 +187,8 @@ class UserHostingKeyForm(forms.ModelForm): alerts the user of it. :return: """ - if 'generate' in self.request.POST: + if ('generate' in self.request.POST + or not self.fields['public_key'].required): return self.data.get('public_key') KEY_ERROR_MESSAGE = _("Please input a proper SSH key") openssh_pubkey_str = self.data.get('public_key').strip() diff --git a/hosting/views.py b/hosting/views.py index 33a8748e..f5146fbf 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1004,13 +1004,16 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): def post(self, request): # Check ssh public key and then proceed form = self.get_form() - required = 'add_ssh' in self.request.POST + required = True - # SSH key is required only if the user doesn't have an existing - # key - if len(get_all_public_keys(self.request.user)) == 0: - form.fields['name'].required = required - form.fields['public_key'].required = required + # SSH key validation is required only if the user doesn't have an + # existing key and user has input some value in the add ssh key fields + if (len(get_all_public_keys(self.request.user)) > 0 and + (len(form.data.get('public_key')) == 0 and + len(form.data.get('name')) == 0)): + required = False + form.fields['name'].required = required + form.fields['public_key'].required = required if not form.is_valid(): response = { 'status': False, @@ -1019,9 +1022,10 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): } return JsonResponse(response) - # We have a valid SSH key from the user, save it in opennebula and db - # and proceed further - form.save() + if required: + # We have a valid SSH key from the user, save it in opennebula and + # db and proceed further + form.save() template = request.session.get('template') specs = request.session.get('specs') From d5d90e0790c70ca11c31a4402c61c9046ca93776 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 06:43:49 +0530 Subject: [PATCH 061/626] Add datacenterlight url: /add-ssh-key --- datacenterlight/urls.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/datacenterlight/urls.py b/datacenterlight/urls.py index 006e7fc3..d20366bf 100644 --- a/datacenterlight/urls.py +++ b/datacenterlight/urls.py @@ -3,10 +3,9 @@ from django.views.generic import TemplateView, RedirectView from .views import ( IndexView, PaymentOrderView, OrderConfirmationView, - WhyDataCenterLightView, ContactUsView + WhyDataCenterLightView, ContactUsView, AskSSHKeyView ) - urlpatterns = [ url(r'^$', IndexView.as_view(), name='index'), url(r'^t/$', IndexView.as_view(), name='index_t'), @@ -20,6 +19,8 @@ urlpatterns = [ url(r'^payment/?$', PaymentOrderView.as_view(), name='payment'), url(r'^order-confirmation/?$', OrderConfirmationView.as_view(), name='order_confirmation'), + url(r'^add-ssh-key/?$', AskSSHKeyView.as_view(), + name='add_ssh_key'), url(r'^contact/?$', ContactUsView.as_view(), name='contact_us'), url(r'glasfaser/?$', TemplateView.as_view(template_name='ungleich_page/glasfaser.html'), From f502e53845b32f7807ed3d71a52c45b826074272 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 06:45:48 +0530 Subject: [PATCH 062/626] Add basic implementation of AskSSHKeyView --- datacenterlight/views.py | 22 +++++++++++++++++++++- hosting/templates/hosting/user_key.html | 3 ++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 76f50aec..d76303f7 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -523,13 +523,33 @@ class PaymentOrderView(FormView): else: request.session['customer'] = customer return HttpResponseRedirect( - reverse('datacenterlight:order_confirmation')) + reverse('datacenterlight:add_ssh_key')) else: context = self.get_context_data() context['billing_address_form'] = address_form return self.render_to_response(context) +class AskSSHKeyView(FormView): + form_class = UserHostingKeyForm + template_name = "datacenterlight/add_ssh_key.html" + + def get_form_kwargs(self): + kwargs = super(AskSSHKeyView, self).get_form_kwargs() + kwargs.update({'request': self.request}) + return kwargs + + @cache_control(no_cache=True, must_revalidate=True, no_store=True) + def get(self, request, *args, **kwargs): + context = { + 'site_url': reverse('datacenterlight:index'), + 'cms_integration': get_cms_integration('default'), + 'form': UserHostingKeyForm(request=self.request), + 'keys': get_all_public_keys(self.request.user) + } + return render(request, self.template_name, context) + + class OrderConfirmationView(DetailView, FormView): form_class = UserHostingKeyForm template_name = "datacenterlight/order_detail.html" diff --git a/hosting/templates/hosting/user_key.html b/hosting/templates/hosting/user_key.html index 804d661a..247551b5 100644 --- a/hosting/templates/hosting/user_key.html +++ b/hosting/templates/hosting/user_key.html @@ -8,7 +8,8 @@ {% csrf_token %} {% if messages %}
From b6eb72af7d389da94a5c853b3746258a503fdd3d Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 08:08:43 +0530 Subject: [PATCH 063/626] Refactor SSHKeyCreateView to utils Common between hosting/datacenterlight apps --- utils/views.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/utils/views.py b/utils/views.py index 394a9fc2..93d778b4 100644 --- a/utils/views.py +++ b/utils/views.py @@ -1,16 +1,26 @@ +import uuid + from django.conf import settings from django.contrib import messages from django.contrib.auth import authenticate, login +from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.tokens import default_token_generator +from django.core.files.base import ContentFile from django.core.urlresolvers import reverse_lazy from django.http import HttpResponseRedirect +from django.shortcuts import render from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.utils.translation import ugettext_lazy as _ -from django.views.generic import FormView, CreateView from django.views.decorators.cache import cache_control +from django.views.generic import FormView, CreateView +from datacenterlight.utils import get_cms_integration +from hosting.forms import UserHostingKeyForm +from hosting.models import UserHostingKey from membership.models import CustomUser +from opennebula_api.models import OpenNebulaManager +from utils.hosting_utils import get_all_public_keys from .forms import SetPasswordForm from .mailer import BaseEmail @@ -174,3 +184,107 @@ class PasswordResetConfirmViewMixin(FormView): form.add_error(None, _('The reset password link is no longer valid.')) return self.form_invalid(form) + + +class SSHKeyCreateView(LoginRequiredMixin, FormView): + form_class = UserHostingKeyForm + model = UserHostingKey + template_name = 'hosting/user_key.html' + login_url = reverse_lazy('hosting:login') + context_object_name = "virtual_machine" + success_url = reverse_lazy('hosting:ssh_keys') + + def get_form_kwargs(self): + kwargs = super(SSHKeyCreateView, self).get_form_kwargs() + kwargs.update({'request': self.request}) + return kwargs + + def form_valid(self, form): + form.save() + if settings.DCL_SSH_KEY_NAME_PREFIX in form.instance.name: + content = ContentFile(form.cleaned_data.get('private_key')) + filename = form.cleaned_data.get( + 'name') + '_' + str(uuid.uuid4())[:8] + '_private.pem' + form.instance.private_key.save(filename, content) + context = self.get_context_data() + + next_url = self.request.session.get( + 'next', + reverse_lazy('hosting:create_virtual_machine') + ) + + if 'next' in self.request.session: + context.update({ + 'next_url': next_url + }) + del (self.request.session['next']) + + if form.cleaned_data.get('private_key'): + context.update({ + 'private_key': form.cleaned_data.get('private_key'), + 'key_name': form.cleaned_data.get('name'), + 'form': UserHostingKeyForm(request=self.request), + }) + + owner = self.request.user + manager = OpenNebulaManager( + email=owner.email, + password=owner.password + ) + keys_to_save = get_all_public_keys(self.request.user) + manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) + return HttpResponseRedirect(self.success_url) + + def post(self, request, *args, **kwargs): + form = self.get_form() + required = 'add_ssh' in self.request.POST + form.fields['name'].required = required + form.fields['public_key'].required = required + if form.is_valid(): + return self.form_valid(form) + else: + return self.form_invalid(form) + + +class AskSSHKeyView(SSHKeyCreateView): + form_class = UserHostingKeyForm + template_name = "datacenterlight/add_ssh_key.html" + success_url = reverse_lazy('datacenterlight:order_confirmation') + context_object_name = "dcl_vm_buy_add_ssh_key" + + @cache_control(no_cache=True, must_revalidate=True, no_store=True) + def get(self, request, *args, **kwargs): + context = { + 'site_url': reverse_lazy('datacenterlight:index'), + 'cms_integration': get_cms_integration('default'), + 'form': UserHostingKeyForm(request=self.request), + 'keys': get_all_public_keys(self.request.user) + } + return render(request, self.template_name, context) + # + # def post(self, request, *args, **kwargs): + # # Check ssh public key and then proceed + # form = self.get_form() + # required = True + # + # # SSH key validation is required only if the user doesn't have an + # # existing key and user has input some value in the add ssh key fields + # if (len(get_all_public_keys(self.request.user)) > 0 and + # (len(form.data.get('public_key')) == 0 and + # len(form.data.get('name')) == 0)): + # required = False + # form.fields['name'].required = required + # form.fields['public_key'].required = required + # if not form.is_valid(): + # response = { + # 'status': False, + # 'msg_title': str(_('SSH key related error occurred')), + # 'msg_body': "
".join([str(v) for k,v in form.errors.items()]), + # } + # return JsonResponse(response) + # + # if required: + # # We have a valid SSH key from the user, save it in opennebula and + # # db and proceed further + # form.save() + From 47fd9a8f28d85f580bad8947a0aea15435f70ce3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 08:09:37 +0530 Subject: [PATCH 064/626] Adjust urls in datacenterlight/hosting apps urls/views after refactor --- datacenterlight/urls.py | 3 +- datacenterlight/views.py | 20 ------------- hosting/urls.py | 5 +++- hosting/views.py | 61 ---------------------------------------- 4 files changed, 6 insertions(+), 83 deletions(-) diff --git a/datacenterlight/urls.py b/datacenterlight/urls.py index d20366bf..13296de7 100644 --- a/datacenterlight/urls.py +++ b/datacenterlight/urls.py @@ -1,9 +1,10 @@ from django.conf.urls import url from django.views.generic import TemplateView, RedirectView +from utils.views import AskSSHKeyView from .views import ( IndexView, PaymentOrderView, OrderConfirmationView, - WhyDataCenterLightView, ContactUsView, AskSSHKeyView + WhyDataCenterLightView, ContactUsView ) urlpatterns = [ diff --git a/datacenterlight/views.py b/datacenterlight/views.py index d76303f7..99ceae8f 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -530,26 +530,6 @@ class PaymentOrderView(FormView): return self.render_to_response(context) -class AskSSHKeyView(FormView): - form_class = UserHostingKeyForm - template_name = "datacenterlight/add_ssh_key.html" - - def get_form_kwargs(self): - kwargs = super(AskSSHKeyView, self).get_form_kwargs() - kwargs.update({'request': self.request}) - return kwargs - - @cache_control(no_cache=True, must_revalidate=True, no_store=True) - def get(self, request, *args, **kwargs): - context = { - 'site_url': reverse('datacenterlight:index'), - 'cms_integration': get_cms_integration('default'), - 'form': UserHostingKeyForm(request=self.request), - 'keys': get_all_public_keys(self.request.user) - } - return render(request, self.template_name, context) - - class OrderConfirmationView(DetailView, FormView): form_class = UserHostingKeyForm template_name = "datacenterlight/order_detail.html" diff --git a/hosting/urls.py b/hosting/urls.py index 4779f67c..5b2b87b0 100644 --- a/hosting/urls.py +++ b/hosting/urls.py @@ -1,6 +1,7 @@ from django.conf.urls import url from django.contrib.auth import views as auth_views +from utils.views import SSHKeyCreateView, AskSSHKeyView from .views import ( DjangoHostingView, RailsHostingView, PaymentVMView, NodeJSHostingView, LoginView, SignupView, SignupValidateView, SignupValidatedView, IndexView, @@ -8,7 +9,7 @@ from .views import ( VirtualMachinesPlanListView, VirtualMachineView, OrdersHostingDeleteView, MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView, CreateVirtualMachinesView, HostingBillListView, - HostingBillDetailView, SSHKeyDeleteView, SSHKeyCreateView, SSHKeyListView, + HostingBillDetailView, SSHKeyDeleteView, SSHKeyListView, SSHKeyChoiceView, DashboardView, SettingsView, ResendActivationEmailView, InvoiceListView, InvoiceDetailView, CheckUserVM ) @@ -27,6 +28,8 @@ urlpatterns = [ url(r'invoices/?$', InvoiceListView.as_view(), name='invoices'), url(r'order-confirmation/?$', OrdersHostingDetailView.as_view(), name='order-confirmation'), + url(r'^add-ssh-key/?$', AskSSHKeyView.as_view(), + name='add_ssh_key'), url(r'orders/(?P\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'), url(r'invoice/(?P[-\w]+)/?$', InvoiceDetailView.as_view(), diff --git a/hosting/views.py b/hosting/views.py index f5146fbf..97b7f42c 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -523,67 +523,6 @@ class SSHKeyChoiceView(LoginRequiredMixin, View): return redirect(reverse_lazy('hosting:ssh_keys'), foo='bar') -@method_decorator(decorators, name='dispatch') -class SSHKeyCreateView(LoginRequiredMixin, FormView): - form_class = UserHostingKeyForm - model = UserHostingKey - template_name = 'hosting/user_key.html' - login_url = reverse_lazy('hosting:login') - context_object_name = "virtual_machine" - success_url = reverse_lazy('hosting:ssh_keys') - - def get_form_kwargs(self): - kwargs = super(SSHKeyCreateView, self).get_form_kwargs() - kwargs.update({'request': self.request}) - return kwargs - - def form_valid(self, form): - form.save() - if settings.DCL_SSH_KEY_NAME_PREFIX in form.instance.name: - content = ContentFile(form.cleaned_data.get('private_key')) - filename = form.cleaned_data.get( - 'name') + '_' + str(uuid.uuid4())[:8] + '_private.pem' - form.instance.private_key.save(filename, content) - context = self.get_context_data() - - next_url = self.request.session.get( - 'next', - reverse('hosting:create_virtual_machine') - ) - - if 'next' in self.request.session: - context.update({ - 'next_url': next_url - }) - del (self.request.session['next']) - - if form.cleaned_data.get('private_key'): - context.update({ - 'private_key': form.cleaned_data.get('private_key'), - 'key_name': form.cleaned_data.get('name'), - 'form': UserHostingKeyForm(request=self.request), - }) - - owner = self.request.user - manager = OpenNebulaManager( - email=owner.email, - password=owner.password - ) - keys_to_save = get_all_public_keys(self.request.user) - manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) - return HttpResponseRedirect(self.success_url) - - def post(self, request, *args, **kwargs): - form = self.get_form() - required = 'add_ssh' in self.request.POST - form.fields['name'].required = required - form.fields['public_key'].required = required - if form.is_valid(): - return self.form_valid(form) - else: - return self.form_invalid(form) - - @method_decorator(decorators, name='dispatch') class SettingsView(LoginRequiredMixin, FormView): template_name = "hosting/settings.html" From 9b73fa71dc1c2018380523574717daf61a9a3e0c Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 08:10:37 +0530 Subject: [PATCH 065/626] Add datacenterlight add_ssh_key.html template file --- .../templates/datacenterlight/add_ssh_key.html | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 datacenterlight/templates/datacenterlight/add_ssh_key.html diff --git a/datacenterlight/templates/datacenterlight/add_ssh_key.html b/datacenterlight/templates/datacenterlight/add_ssh_key.html new file mode 100644 index 00000000..e083eece --- /dev/null +++ b/datacenterlight/templates/datacenterlight/add_ssh_key.html @@ -0,0 +1,8 @@ + +{% load staticfiles bootstrap3 i18n custom_tags humanize %} + +{% block content %} + {% block userkey_form %} + {% include 'hosting/user_key.html' with title="Your VM is almost ready!" sub_title="You just need to specify your public SSH key." %} + {% endblock userkey_form %} +{%endblock%} \ No newline at end of file From 5fcd0d6b18e08dc2bcc68b118f6d2d1347a42791 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 08:18:10 +0530 Subject: [PATCH 066/626] Remove add SSH key form in the order confirmation --- .../datacenterlight/order_detail.html | 32 ------------------- hosting/templates/hosting/order_detail.html | 29 ----------------- 2 files changed, 61 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index e1cc5853..31933e12 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -134,38 +134,6 @@
{% csrf_token %} - {% if generic_payment_details %} - {% else %} - {% comment %} - We are in VM buy flow and we want user to click the "Place order" button. - At this point, we also want the user to input the SSH key for the VM. - {% endcomment %} - - {% if messages %} -
- {% for message in messages %} - {{ message }} - {% endfor %} -
- {% endif %} -
-

 {% trans "Add your public SSH key" %}

-
-
- {% if keys|length > 0 %} -
Existing keys
- {% endif %} - {% for key in keys %} - -
- {% endfor %} -
- {% for field in form %} - {% bootstrap_field field %} - {% endfor %} - {% endif %}
{% if generic_payment_details %} diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index 7ab79378..4a62e9fa 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -198,35 +198,6 @@ {% block submit_btn %} {% csrf_token %} - {% comment %} - We are in VM buy flow and we want user to click the "Place order" button. - At this point, we also want the user to input the SSH key for the VM. - {% endcomment %} - - {% if messages %} -
- {% for message in messages %} - {{ message }} - {% endfor %} -
- {% endif %} -
-

 {% trans "Add your public SSH key" %}

-
-
- {% if keys|length > 0 %} -
Existing keys
- {% endif %} - {% for key in keys %} - -
- {% endfor %} -
- {% for field in form %} - {% bootstrap_field field %} - {% endfor %}
{% blocktrans with vm_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{ vm_price }} CHF/month{% endblocktrans %}.
From 79eba3b70c80c10d1f1a22713565d50ebd02a8e0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 08:28:28 +0530 Subject: [PATCH 067/626] Remove add SSH key form in order confirmation related code --- datacenterlight/views.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 99ceae8f..28372a89 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -588,31 +588,6 @@ class OrderConfirmationView(DetailView, FormView): return render(request, self.template_name, context) def post(self, request, *args, **kwargs): - # Check ssh public key and then proceed - form = self.get_form() - required = True - - # SSH key validation is required only if the user doesn't have an - # existing key and user has input some value in the add ssh key fields - if (len(get_all_public_keys(self.request.user)) > 0 and - (len(form.data.get('public_key')) == 0 and - len(form.data.get('name')) == 0)): - required = False - form.fields['name'].required = required - form.fields['public_key'].required = required - if not form.is_valid(): - response = { - 'status': False, - 'msg_title': str(_('SSH key related error occurred')), - 'msg_body': "
".join([str(v) for k,v in form.errors.items()]), - } - return JsonResponse(response) - - if required: - # We have a valid SSH key from the user, save it in opennebula and - # db and proceed further - form.save() - user = request.session.get('user') stripe_api_cus_id = request.session.get('customer') stripe_utils = StripeUtils() From 2c74eae3f90dc049a15a1186fe007debfb657a5f Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 08:56:43 +0530 Subject: [PATCH 068/626] Remove commented code --- utils/views.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/utils/views.py b/utils/views.py index 93d778b4..00d97bbd 100644 --- a/utils/views.py +++ b/utils/views.py @@ -261,30 +261,4 @@ class AskSSHKeyView(SSHKeyCreateView): 'keys': get_all_public_keys(self.request.user) } return render(request, self.template_name, context) - # - # def post(self, request, *args, **kwargs): - # # Check ssh public key and then proceed - # form = self.get_form() - # required = True - # - # # SSH key validation is required only if the user doesn't have an - # # existing key and user has input some value in the add ssh key fields - # if (len(get_all_public_keys(self.request.user)) > 0 and - # (len(form.data.get('public_key')) == 0 and - # len(form.data.get('name')) == 0)): - # required = False - # form.fields['name'].required = required - # form.fields['public_key'].required = required - # if not form.is_valid(): - # response = { - # 'status': False, - # 'msg_title': str(_('SSH key related error occurred')), - # 'msg_body': "
".join([str(v) for k,v in form.errors.items()]), - # } - # return JsonResponse(response) - # - # if required: - # # We have a valid SSH key from the user, save it in opennebula and - # # db and proceed further - # form.save() From 207c3a6c6c7930b710eadb351e65b542c5dfc46b Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 08:57:39 +0530 Subject: [PATCH 069/626] Skip SSH key page for generic products page --- datacenterlight/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 28372a89..edc95ee9 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -522,6 +522,12 @@ class PaymentOrderView(FormView): request.session['customer'] = customer.stripe_id else: request.session['customer'] = customer + + # For generic payment we take the user directly to confirmation + if ('generic_payment_type' in request.session and + self.request.session['generic_payment_type'] == 'generic'): + return HttpResponseRedirect( + reverse('datacenterlight:order_confirmation')) return HttpResponseRedirect( reverse('datacenterlight:add_ssh_key')) else: From c35bc79c5c642e2ba6e590a7158c9c45efa24f6c Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 20:28:26 +0530 Subject: [PATCH 070/626] Set success_url based on flow: hosting vs landing --- utils/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/views.py b/utils/views.py index 00d97bbd..e5eca845 100644 --- a/utils/views.py +++ b/utils/views.py @@ -262,3 +262,6 @@ class AskSSHKeyView(SSHKeyCreateView): } return render(request, self.template_name, context) + def post(self, request, *args, **kwargs): + self.success_url = self.request.get("order_confirm_url") + return super(AskSSHKeyView, self) \ No newline at end of file From c285e1d9eb8df95695bafe6c94e894338ed2ef0d Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 20:30:06 +0530 Subject: [PATCH 071/626] Set respective order_confirm_url for landing vs hosting flows For hosting flow also take the user to add_ssh_key after payment --- datacenterlight/views.py | 6 ++++-- hosting/views.py | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index edc95ee9..7b67acd5 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -528,8 +528,10 @@ class PaymentOrderView(FormView): self.request.session['generic_payment_type'] == 'generic'): return HttpResponseRedirect( reverse('datacenterlight:order_confirmation')) - return HttpResponseRedirect( - reverse('datacenterlight:add_ssh_key')) + else: + self.request.session['order_confirm_url'] = reverse('datacenterlight:order_confirmation') + return HttpResponseRedirect( + reverse('datacenterlight:add_ssh_key')) else: context = self.get_context_data() context['billing_address_form'] = address_form diff --git a/hosting/views.py b/hosting/views.py index 97b7f42c..9f7ce8cc 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -769,10 +769,10 @@ class PaymentVMView(LoginRequiredMixin, FormView): reverse('hosting:payment') + '#payment_error') request.session['token'] = token request.session['billing_address_data'] = billing_address_data - return HttpResponseRedirect("{url}?{query_params}".format( - url=reverse('hosting:order-confirmation'), - query_params='page=payment') - ) + self.request.session['order_confirm_url'] = "{url}?{query_params}".format( + url=reverse('hosting:order-confirmation'), + query_params='page=payment') + return HttpResponseRedirect(reverse('hosting:add_ssh_key')) else: return self.form_invalid(form) From d9a2c5216ce4afbd8031cfb6e165bef3f5725c66 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 20:30:35 +0530 Subject: [PATCH 072/626] Also remove order_confirm_url from session vars --- datacenterlight/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 208d39f3..4e56e812 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -1,8 +1,9 @@ import logging + import pyotp import requests -from django.contrib.sites.models import Site from django.conf import settings +from django.contrib.sites.models import Site from datacenterlight.tasks import create_vm_task from hosting.models import HostingOrder, HostingBill, OrderDetail @@ -99,7 +100,8 @@ def clear_all_session_vars(request): for session_var in ['specs', 'template', 'billing_address', 'billing_address_data', 'card_id', 'token', 'customer', 'generic_payment_type', - 'generic_payment_details', 'product_id']: + 'generic_payment_details', 'product_id', + 'order_confirm_url']: if session_var in request.session: del request.session[session_var] From c8c5bb763a9dd46adcfb79548cc41c4d1fad301e Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 20:36:13 +0530 Subject: [PATCH 073/626] Remove Add SSH key form in "Order Confirm" page related code (not needed) --- hosting/views.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 9f7ce8cc..201d58e7 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -941,31 +941,6 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): @method_decorator(decorators) def post(self, request): - # Check ssh public key and then proceed - form = self.get_form() - required = True - - # SSH key validation is required only if the user doesn't have an - # existing key and user has input some value in the add ssh key fields - if (len(get_all_public_keys(self.request.user)) > 0 and - (len(form.data.get('public_key')) == 0 and - len(form.data.get('name')) == 0)): - required = False - form.fields['name'].required = required - form.fields['public_key'].required = required - if not form.is_valid(): - response = { - 'status': False, - 'msg_title': str(_('SSH key related error occurred')), - 'msg_body': "
".join([str(v) for k,v in form.errors.items()]), - } - return JsonResponse(response) - - if required: - # We have a valid SSH key from the user, save it in opennebula and - # db and proceed further - form.save() - template = request.session.get('template') specs = request.session.get('specs') stripe_utils = StripeUtils() From 561178e473a78834e2552d103ceae856624ad887 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 20:48:34 +0530 Subject: [PATCH 074/626] Set success_url from session and call super post method --- utils/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/views.py b/utils/views.py index e5eca845..01f4968c 100644 --- a/utils/views.py +++ b/utils/views.py @@ -263,5 +263,5 @@ class AskSSHKeyView(SSHKeyCreateView): return render(request, self.template_name, context) def post(self, request, *args, **kwargs): - self.success_url = self.request.get("order_confirm_url") - return super(AskSSHKeyView, self) \ No newline at end of file + self.success_url = self.request.session.get("order_confirm_url") + return super(AskSSHKeyView, self).post(self, request, *args, **kwargs) \ No newline at end of file From 8efe978b23824cd44abb5eeefab1002d5b5f3b51 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 21:01:41 +0530 Subject: [PATCH 075/626] Don't make SSHKeyCreateView with LoginRequiredMixin --- utils/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/utils/views.py b/utils/views.py index 01f4968c..0429c7db 100644 --- a/utils/views.py +++ b/utils/views.py @@ -3,7 +3,6 @@ import uuid from django.conf import settings from django.contrib import messages from django.contrib.auth import authenticate, login -from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.tokens import default_token_generator from django.core.files.base import ContentFile from django.core.urlresolvers import reverse_lazy @@ -186,7 +185,7 @@ class PasswordResetConfirmViewMixin(FormView): return self.form_invalid(form) -class SSHKeyCreateView(LoginRequiredMixin, FormView): +class SSHKeyCreateView(FormView): form_class = UserHostingKeyForm model = UserHostingKey template_name = 'hosting/user_key.html' From a20dbc1f960ba22a2e1d59a5c8335027800ac4fd Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 23:08:35 +0530 Subject: [PATCH 076/626] Cleanup new_user_hosting_key_id session variable also --- datacenterlight/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 4e56e812..11d2b82e 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -101,7 +101,7 @@ def clear_all_session_vars(request): 'billing_address_data', 'card_id', 'token', 'customer', 'generic_payment_type', 'generic_payment_details', 'product_id', - 'order_confirm_url']: + 'order_confirm_url', 'new_user_hosting_key_id']: if session_var in request.session: del request.session[session_var] From 670c2b18a903ade0f4e0ccc11d7355658de047e2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 23:09:42 +0530 Subject: [PATCH 077/626] Set user_hosting_key's user to the newly created user --- datacenterlight/views.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 7b67acd5..22294432 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -17,9 +17,10 @@ from hosting.forms import ( UserHostingKeyForm ) from hosting.models import ( - HostingBill, HostingOrder, UserCardDetail, GenericProduct + HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey ) from membership.models import CustomUser, StripeCustomer +from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import VMTemplateSerializer from utils.forms import ( BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm, @@ -847,6 +848,18 @@ class OrderConfirmationView(DetailView, FormView): new_user = authenticate(username=custom_user.email, password=password) login(request, new_user) + if 'new_user_hosting_key_id' in self.request.session: + user_hosting_key = UserHostingKey.objects.get(self.request.session['new_user_hosting_key_id']) + user_hosting_key.user = new_user + user_hosting_key.save() + + owner = new_user + manager = OpenNebulaManager( + email=owner.email, + password=owner.password + ) + keys_to_save = get_all_public_keys(new_user) + manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) else: # We assume that if the user is here, his/her StripeCustomer # object already exists From 32de20aaba11e1899916525db437176ed7ac2292 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 23:11:14 +0530 Subject: [PATCH 078/626] Set unon authenticated user to NONE --- hosting/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/forms.py b/hosting/forms.py index 797bc700..1bc99b8f 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -222,7 +222,7 @@ class UserHostingKeyForm(forms.ModelForm): return self.data.get('name') def clean_user(self): - return self.request.user + return self.request.user if self.request.user.is_authenticated() else None def clean(self): cleaned_data = self.cleaned_data From ddaa3206285b5cf5aa422efb08030568c2bc85e5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 23:11:49 +0530 Subject: [PATCH 079/626] Set user foreign key to be blank allowing null values --- hosting/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hosting/models.py b/hosting/models.py index aead430c..311ee953 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -1,11 +1,11 @@ import json import logging import os -import pytz +from datetime import datetime +import pytz from Crypto.PublicKey import RSA from dateutil.relativedelta import relativedelta -from datetime import datetime from django.db import models from django.utils import timezone from django.utils.functional import cached_property @@ -187,7 +187,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model): class UserHostingKey(models.Model): - user = models.ForeignKey(CustomUser) + user = models.ForeignKey(CustomUser, blank=True, null=True) public_key = models.TextField() private_key = models.FileField(upload_to='private_keys', blank=True) created_at = models.DateTimeField(auto_now_add=True) From 26fab27c3fb476c255b02f5fec343d2e2d31d9e7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 1 Jul 2019 23:17:27 +0530 Subject: [PATCH 080/626] Set new_user_hosting_key_id session variable to track newly created key --- utils/views.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/utils/views.py b/utils/views.py index 0429c7db..05d0fdc2 100644 --- a/utils/views.py +++ b/utils/views.py @@ -225,13 +225,16 @@ class SSHKeyCreateView(FormView): 'form': UserHostingKeyForm(request=self.request), }) - owner = self.request.user - manager = OpenNebulaManager( - email=owner.email, - password=owner.password - ) - keys_to_save = get_all_public_keys(self.request.user) - manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) + if self.request.user.is_authenticated(): + owner = self.request.user + manager = OpenNebulaManager( + email=owner.email, + password=owner.password + ) + keys_to_save = get_all_public_keys(self.request.user) + manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) + else: + self.request.session["new_user_hosting_key_id"] = form.instance.id return HttpResponseRedirect(self.success_url) def post(self, request, *args, **kwargs): From dfb16f0c25d0ca9b880efc14467c4d234d905055 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Jul 2019 05:13:02 +0530 Subject: [PATCH 081/626] Save new user's hosting key only in that case --- datacenterlight/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 22294432..3763d465 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -848,10 +848,10 @@ class OrderConfirmationView(DetailView, FormView): new_user = authenticate(username=custom_user.email, password=password) login(request, new_user) - if 'new_user_hosting_key_id' in self.request.session: - user_hosting_key = UserHostingKey.objects.get(self.request.session['new_user_hosting_key_id']) - user_hosting_key.user = new_user - user_hosting_key.save() + if 'new_user_hosting_key_id' in self.request.session: + user_hosting_key = UserHostingKey.objects.get(id=self.request.session['new_user_hosting_key_id']) + user_hosting_key.user = new_user + user_hosting_key.save() owner = new_user manager = OpenNebulaManager( From 921d832f9e9ab553771f3874778174e77ce07b80 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Jul 2019 06:44:31 +0530 Subject: [PATCH 082/626] Make user in UserHostingKey model nullable --- hosting/migrations/0055_auto_20190701_1614.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 hosting/migrations/0055_auto_20190701_1614.py diff --git a/hosting/migrations/0055_auto_20190701_1614.py b/hosting/migrations/0055_auto_20190701_1614.py new file mode 100644 index 00000000..4a2744fb --- /dev/null +++ b/hosting/migrations/0055_auto_20190701_1614.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-07-01 16:14 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0054_auto_20190508_2141'), + ] + + operations = [ + migrations.AlterField( + model_name='userhostingkey', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] From 44921014a21d074e9e2510186004d252e278336d Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Jul 2019 07:09:44 +0530 Subject: [PATCH 083/626] Get the correct opennebula user id --- opennebula_api/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 478758fc..f8ef6481 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -376,7 +376,7 @@ class OpenNebulaManager(): """ return_value = self.oneadmin_client.call( 'user.update', - self.opennebula_user.id, + self.opennebula_user if type(self.opennebula_user) == int else self.opennebula_user.id, '%s' % ssh_key, update_type ) From 6f49157ddd81522982f70ede42afe604d22d8411 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Jul 2019 16:27:12 +0530 Subject: [PATCH 084/626] Update add_ssh_key subtitle --- datacenterlight/templates/datacenterlight/add_ssh_key.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/templates/datacenterlight/add_ssh_key.html b/datacenterlight/templates/datacenterlight/add_ssh_key.html index e083eece..936bd782 100644 --- a/datacenterlight/templates/datacenterlight/add_ssh_key.html +++ b/datacenterlight/templates/datacenterlight/add_ssh_key.html @@ -3,6 +3,6 @@ {% block content %} {% block userkey_form %} - {% include 'hosting/user_key.html' with title="Your VM is almost ready!" sub_title="You just need to specify your public SSH key." %} + {% include 'hosting/user_key.html' with title="Your VM is almost ready!" sub_title="You need to specify your public SSH key to access your VM. You can either add your existing key, or generate a new key pair by clicking the generate button below. After choosing your public SSH key option you’ll be directed to the order confirmation page." %} {% endblock userkey_form %} {%endblock%} \ No newline at end of file From b0548f4cfa3f4d5b3fa9c86188ed4eae4eeec5c0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Jul 2019 16:51:29 +0530 Subject: [PATCH 085/626] Translate ssh key form title and subtitles --- datacenterlight/templates/datacenterlight/add_ssh_key.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/datacenterlight/templates/datacenterlight/add_ssh_key.html b/datacenterlight/templates/datacenterlight/add_ssh_key.html index 936bd782..44048bad 100644 --- a/datacenterlight/templates/datacenterlight/add_ssh_key.html +++ b/datacenterlight/templates/datacenterlight/add_ssh_key.html @@ -3,6 +3,8 @@ {% block content %} {% block userkey_form %} - {% include 'hosting/user_key.html' with title="Your VM is almost ready!" sub_title="You need to specify your public SSH key to access your VM. You can either add your existing key, or generate a new key pair by clicking the generate button below. After choosing your public SSH key option you’ll be directed to the order confirmation page." %} + {% with form_title=_("Your VM is almost ready!") form_sub_title=_("You need to specify your public SSH key to access your VM. You can either add your existing key, or generate a new key pair by clicking the generate button below. After choosing your public SSH key option you’ll be directed to the order confirmation page.") %} + {% include 'hosting/user_key.html' with title=form_title sub_title=form_sub_title %} + {% endwith %} {% endblock userkey_form %} {%endblock%} \ No newline at end of file From 69401a1cc6ee6cca7417b15de47afd38f81f3b3b Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Jul 2019 16:52:08 +0530 Subject: [PATCH 086/626] Make messages for datacenterlight and add some DE translations --- .../locale/de/LC_MESSAGES/django.po | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index d43e91ea..d6a25b53 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: 2018-09-26 20:44+0000\n" +"POT-Creation-Date: 2019-07-03 11:18+0000\n" "PO-Revision-Date: 2018-03-30 23:22+0000\n" "Last-Translator: b'Anonymous User '\n" "Language-Team: LANGUAGE \n" @@ -26,6 +26,22 @@ msgstr "" msgid "Your New VM %(vm_name)s at Data Center Light" msgstr "Deine neue VM %(vm_name)s bei Data Center Light" +msgid "Your VM is almost ready!" +msgstr "" + +msgid "" +"You need to specify your public SSH key to access your VM. You can either " +"add your existing key, or generate a new key pair by clicking the generate " +"button below. After choosing your public SSH key option you’ll be directed " +"to the order confirmation page." +msgstr "" +"Du musst deinen öffentlichen SSH-Schlüssel angeben, um auf deine VM " +"zugreifen zu können. Du kannst entweder deinen vorhandenen Schlüssel " +"hinzufügen oder ein neues Schlüsselpaar generieren, indem du auf die " +"Schaltfläche \"Generieren\" unten klickst. Nachdem du deine öffentliche SSH-" +"Schlüsseloption ausgewählt hast, wirst du zur Bestellbestätigungsseite " +"weitergeleitet. " + msgid "All Rights Reserved" msgstr "Alle Rechte vorbehalten" @@ -134,6 +150,10 @@ msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!" msgid "ORDER VM" msgstr "VM BESTELLEN" +#, python-format +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 "" @@ -152,9 +172,6 @@ msgstr "Standort: Schweiz" msgid "Please enter a value in range 1 - 48." msgstr "Bitte gib einen Wert von 1 bis 48 ein." -msgid "Please enter a value in range 1 - 200." -msgstr "Bitte gib einen Wert von 1 bis 200 ein." - msgid "Please enter a value in range 10 - 2000." msgstr "Bitte gib einen Wert von 10 bis 2000 ein." @@ -413,6 +430,10 @@ msgstr "Zwischensumme" msgid "VAT" msgstr "Mehrwertsteuer" +#, fuzzy, python-format +#| msgid "" +#| "By clicking \"Place order\" this plan will charge your credit card " +#| "account with %(total_price)s CHF/month" msgid "" "By clicking \"Place order\" this plan will charge your credit card account " "with %(total_price)s CHF/month" @@ -420,6 +441,10 @@ msgstr "" "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " "%(vm_total_price)s CHF pro Monat belastet" +#, fuzzy, python-format +#| msgid "" +#| "By clicking \"Place order\" this payment will charge your credit card " +#| "account with a one time amount of %(total_price)s CHF" msgid "" "By clicking \"Place order\" this payment will charge your credit card " "account with a one time amount of %(total_price)s CHF" @@ -535,6 +560,9 @@ msgstr "Tagen sagen mehr als Worte – Teste jetzt unsere VM!" msgid "Invalid number of cores" msgstr "Ungültige Anzahle CPU-Kerne" +msgid "Invalid calculator properties" +msgstr "" + msgid "Invalid RAM size" msgstr "Ungültige RAM-Grösse" From 2a4fb8c8dee0372a9e547c016ff1e57b183eb6d3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Jul 2019 16:57:00 +0530 Subject: [PATCH 087/626] Add DE translation Your VM is almost ready! => Ihre VM ist fast fertig! --- 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 d6a25b53..07361e29 100644 --- a/datacenterlight/locale/de/LC_MESSAGES/django.po +++ b/datacenterlight/locale/de/LC_MESSAGES/django.po @@ -27,7 +27,7 @@ msgid "Your New VM %(vm_name)s at Data Center Light" msgstr "Deine neue VM %(vm_name)s bei Data Center Light" msgid "Your VM is almost ready!" -msgstr "" +msgstr "Ihre VM ist fast fertig!" msgid "" "You need to specify your public SSH key to access your VM. You can either " From d3e2074b1616020debbb7cca69b3fe709ddb19cc Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Jul 2019 17:29:42 +0530 Subject: [PATCH 088/626] Update DE translation Your VM is almost ready! => Deine VM ist fast fertig! --- 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 07361e29..b5ff3ca5 100644 --- a/datacenterlight/locale/de/LC_MESSAGES/django.po +++ b/datacenterlight/locale/de/LC_MESSAGES/django.po @@ -27,7 +27,7 @@ msgid "Your New VM %(vm_name)s at Data Center Light" msgstr "Deine neue VM %(vm_name)s bei Data Center Light" msgid "Your VM is almost ready!" -msgstr "Ihre VM ist fast fertig!" +msgstr "Deine VM ist fast fertig!" msgid "" "You need to specify your public SSH key to access your VM. You can either " From 3c63f26d31ce095b93b39f0b383fcfc686a155ce Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Jul 2019 20:59:48 +0530 Subject: [PATCH 089/626] Update Changelog for 2.6 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index cfefdd01..a9916173 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.6: 2019-07-03 + * #5509: Getting rid of our key by still supporting multiple user keys (MR!709) 2.5.11: 2019-06-11 * #6672: [api] Check VM belongs to user in the infrastructure directly (MR!707) * #bugfix: DE translation fix "Learn mehr" -> "Lerne mehr" (MR!708) From 59c45492a935048c942f588afa424e4aed0457ee Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 9 Jul 2019 18:40:41 +0530 Subject: [PATCH 090/626] Add expiry year and month in the settings and order payment pages --- datacenterlight/templates/datacenterlight/order_detail.html | 1 + datacenterlight/views.py | 4 ++++ hosting/templates/hosting/order_detail.html | 1 + hosting/templates/hosting/settings.html | 1 + hosting/views.py | 4 ++++ 5 files changed, 11 insertions(+) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 31933e12..8a444bef 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -41,6 +41,7 @@

{% trans "Payment method" %}:

{{cc_brand|default:_('Credit Card')}} {% trans "ending in" %} ****{{cc_last4}}
+ {% trans "Expiry" %} {{cc_exp_year}}/{{cc_exp_month}}
{{request.user.email}}

diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3763d465..60938ac8 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -568,11 +568,15 @@ class OrderConfirmationView(DetailView, FormView): card_details_response = card_details['response_object'] context['cc_last4'] = card_details_response['last4'] context['cc_brand'] = card_details_response['brand'] + context['cc_exp_year'] = card_details_response['exp_year'] + context['cc_exp_month'] = card_details_response['exp_month'] else: card_id = self.request.session.get('card_id') card_detail = UserCardDetail.objects.get(id=card_id) context['cc_last4'] = card_detail.last4 context['cc_brand'] = card_detail.brand + context['cc_exp_year'] = card_detail.exp_year + context['cc_exp_month'] = card_detail.exp_month if ('generic_payment_type' in request.session and self.request.session['generic_payment_type'] == 'generic'): diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index 4a62e9fa..a84e4e4f 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -82,6 +82,7 @@ {{user.email}} {% else %} {{cc_brand|default:_('Credit Card')}} {% trans "ending in" %} ****{{cc_last4}}
+ {% trans "Expiry" %} {{cc_exp_year}}/{{cc_exp_month}}
{% if request.user.is_authenticated %} {{request.user.email}} {% else %} diff --git a/hosting/templates/hosting/settings.html b/hosting/templates/hosting/settings.html index 56818cbf..5cdd830c 100644 --- a/hosting/templates/hosting/settings.html +++ b/hosting/templates/hosting/settings.html @@ -37,6 +37,7 @@
{% trans "Credit Card" %}
{% trans "Last" %} 4: ***** {{card.last4}}
{% trans "Type" %}: {{card.brand}}
+
{% trans "Expiry" %}: {{card.exp_month}}/{{card.exp_year}}
{% if card_list_len > 1 %} diff --git a/hosting/views.py b/hosting/views.py index 201d58e7..9a2e5b19 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -906,11 +906,15 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): card_details_response = card_details['response_object'] context['cc_last4'] = card_details_response['last4'] context['cc_brand'] = card_details_response['brand'] + context['cc_exp_year'] = card_details_response['exp_year'] + context['cc_exp_month'] = card_details_response['exp_month'] else: card_id = self.request.session.get('card_id') card_detail = UserCardDetail.objects.get(id=card_id) context['cc_last4'] = card_detail.last4 context['cc_brand'] = card_detail.brand + context['cc_exp_year'] = card_detail.exp_year + context['cc_exp_month'] = card_detail.exp_month context['site_url'] = reverse('hosting:create_virtual_machine') context['vm'] = self.request.session.get('specs') return context From fe44908868ae22336e803633751f4ec6bcc773a5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 9 Jul 2019 18:47:54 +0530 Subject: [PATCH 091/626] Add expiry year and month to get_all_cards_list --- hosting/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hosting/models.py b/hosting/models.py index 311ee953..e6e29cbe 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -597,6 +597,8 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): for card in user_card_details: cards_list.append({ 'last4': card.last4, 'brand': card.brand, 'id': card.id, + 'exp_year': card.exp_year, + 'exp_month': card.exp_month, 'preferred': card.preferred }) return cards_list From 903ef48c758e5882ac74b514aa6f3865136568b8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 9 Jul 2019 19:03:09 +0530 Subject: [PATCH 092/626] Format cc month to 2 decimal places --- datacenterlight/views.py | 4 ++-- hosting/models.py | 2 +- hosting/views.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 60938ac8..ae649623 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -569,14 +569,14 @@ class OrderConfirmationView(DetailView, FormView): context['cc_last4'] = card_details_response['last4'] context['cc_brand'] = card_details_response['brand'] context['cc_exp_year'] = card_details_response['exp_year'] - context['cc_exp_month'] = card_details_response['exp_month'] + context['cc_exp_month'] = '{:02d}'.format(card_details_response['exp_month']) else: card_id = self.request.session.get('card_id') card_detail = UserCardDetail.objects.get(id=card_id) context['cc_last4'] = card_detail.last4 context['cc_brand'] = card_detail.brand context['cc_exp_year'] = card_detail.exp_year - context['cc_exp_month'] = card_detail.exp_month + context['cc_exp_month'] ='{:02d}'.format(card_detail.exp_month) if ('generic_payment_type' in request.session and self.request.session['generic_payment_type'] == 'generic'): diff --git a/hosting/models.py b/hosting/models.py index e6e29cbe..7b08948e 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -598,7 +598,7 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): cards_list.append({ 'last4': card.last4, 'brand': card.brand, 'id': card.id, 'exp_year': card.exp_year, - 'exp_month': card.exp_month, + 'exp_month': '{:02d}'.format(card.exp_month), 'preferred': card.preferred }) return cards_list diff --git a/hosting/views.py b/hosting/views.py index 9a2e5b19..87cb948c 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -914,7 +914,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): context['cc_last4'] = card_detail.last4 context['cc_brand'] = card_detail.brand context['cc_exp_year'] = card_detail.exp_year - context['cc_exp_month'] = card_detail.exp_month + 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') return context From b6ec2ac95b4e269806db4c3dc1a04f301f15e66a Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 9 Jul 2019 19:08:19 +0530 Subject: [PATCH 093/626] Add missing cc expiry year month in payment page --- datacenterlight/templates/datacenterlight/landing_payment.html | 1 + hosting/templates/hosting/payment.html | 1 + 2 files changed, 2 insertions(+) diff --git a/datacenterlight/templates/datacenterlight/landing_payment.html b/datacenterlight/templates/datacenterlight/landing_payment.html index fb6d51b0..4e71eab9 100644 --- a/datacenterlight/templates/datacenterlight/landing_payment.html +++ b/datacenterlight/templates/datacenterlight/landing_payment.html @@ -131,6 +131,7 @@
{% trans "Credit Card" %}
{% trans "Last" %} 4: ***** {{card.last4}}
{% trans "Type" %}: {{card.brand}}
+
{% trans "Expiry" %}: {{card.exp_month}}/{{card.exp_year}}
{% trans "SELECT" %} diff --git a/hosting/templates/hosting/payment.html b/hosting/templates/hosting/payment.html index e09775cf..f0512fdb 100644 --- a/hosting/templates/hosting/payment.html +++ b/hosting/templates/hosting/payment.html @@ -131,6 +131,7 @@
{% trans "Credit Card" %}
{% trans "Last" %} 4: ***** {{card.last4}}
{% trans "Type" %}: {{card.brand}}
+
{% trans "Expiry" %}: {{card.exp_month}}/{{card.exp_year}}
{% trans "SELECT" %} From 728fd5850bae755cc3ba63ffe7eb002c97ecd949 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 9 Jul 2019 21:21:22 +0530 Subject: [PATCH 094/626] =?UTF-8?q?Update=20hosting=20django.po=20--=20add?= =?UTF-8?q?=20"Expiry"=20->=20G=C3=BCltig=20bis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hosting/locale/de/LC_MESSAGES/django.po | 214 +++++++++++++++--------- 1 file changed, 133 insertions(+), 81 deletions(-) diff --git a/hosting/locale/de/LC_MESSAGES/django.po b/hosting/locale/de/LC_MESSAGES/django.po index 0e337cfb..14d48da9 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: 2018-09-08 08:45+0000\n" +"POT-Creation-Date: 2019-07-09 15:21+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -27,6 +27,30 @@ msgstr "Dein Account wurde noch nicht aktiviert." msgid "User does not exist" msgstr "Der Benutzer existiert nicht" +msgid "Choose a product" +msgstr "" + +msgid "Amount in CHF" +msgstr "Betrag" + +msgid "Recurring monthly" +msgstr "" + +msgid "Amount field does not match" +msgstr "" + +msgid "Recurring field does not match" +msgstr "" + +msgid "Product name" +msgstr "Produkt" + +msgid "Monthly subscription" +msgstr "" + +msgid "One time payment" +msgstr "" + msgid "Confirm Password" msgstr "Passwort Bestätigung" @@ -52,6 +76,9 @@ msgstr "Bitte verwende einen gültigen SSH-Key" msgid "This key exists already with the name \"%(name)s\"" msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits" +msgid "Comma not accepted in the name of the key" +msgstr "" + msgid "All Rights Reserved" msgstr "Alle Rechte vorbehalten" @@ -209,7 +236,8 @@ msgstr "Du hast eine neue virtuelle Maschine bestellt!" #, python-format msgid "Your order of %(vm_name)s has been charged." -msgstr "Deine Bestellung von %(vm_name)s wurde entgegengenommen." +msgstr "" +"Deine Bestellung von %(vm_name)s wurde entgegengenommen." msgid "You can view your VM detail by clicking the button below." msgstr "Um die Rechnung zu sehen, klicke auf den Button unten." @@ -305,6 +333,100 @@ msgstr "Dashboard" msgid "Logout" msgstr "Abmelden" +#, python-format +msgid "%(page_header_text)s" +msgstr "" + +msgid "Invoice #" +msgstr "Rechnung" + +msgid "Date" +msgstr "Datum" + +msgid "Status" +msgstr "" + +msgid "Terminated" +msgstr "Beendet" + +msgid "Approved" +msgstr "Akzeptiert" + +msgid "Declined" +msgstr "Abgelehnt" + +msgid "Billed to" +msgstr "Rechnungsadresse" + +msgid "Payment method" +msgstr "Bezahlmethode" + +msgid "ending in" +msgstr "endend in" + +msgid "Invoice summary" +msgstr "" + +msgid "Product" +msgstr "Produkt" + +msgid "Period" +msgstr "Periode" + +msgid "Cores" +msgstr "Prozessorkerne" + +msgid "Memory" +msgstr "Arbeitsspeicher" + +msgid "Disk space" +msgstr "Festplattenkapazität" + +msgid "Subtotal" +msgstr "Zwischensumme" + +msgid "VAT" +msgstr "Mehrwertsteuer" + +msgid "Discount" +msgstr "Rabatt" + +msgid "Total" +msgstr "Gesamt" + +msgid "Amount" +msgstr "Betrag" + +msgid "Description" +msgstr "" + +msgid "Recurring" +msgstr "" + +msgid "of every month" +msgstr "" + +msgid "BACK TO LIST" +msgstr "ZURÜCK ZUR LISTE" + +msgid "Some problem encountered. Please try again later." +msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal." + +msgid "VM ID" +msgstr "" + +msgid "IP Address" +msgstr "" + +msgid "See Invoice" +msgstr "Siehe Rechnung" + +msgid "Page" +msgstr "" + +msgid "of" +msgstr "" + msgid "Log in" msgstr "Anmelden" @@ -338,67 +460,15 @@ msgstr "Als gelesen markieren" msgid "All notifications" msgstr "Alle Benachrichtigungen" -#, python-format -msgid "%(page_header_text)s" -msgstr "" - -msgid "Date" -msgstr "Datum" - -msgid "Status" -msgstr "" - -msgid "Terminated" -msgstr "Beendet" - -msgid "Approved" -msgstr "Akzeptiert" - -msgid "Declined" -msgstr "Abgelehnt" - -msgid "Billed to" -msgstr "Rechnungsadresse" - -msgid "Payment method" -msgstr "Bezahlmethode" - -msgid "ending in" -msgstr "endend in" - msgid "Credit Card" msgstr "Kreditkarte" +msgid "Expiry" +msgstr "Gültig bis" + msgid "Order summary" msgstr "Bestellungsübersicht" -msgid "Product" -msgstr "Produkt" - -msgid "Period" -msgstr "Periode" - -msgid "Cores" -msgstr "Prozessorkerne" - -msgid "Memory" -msgstr "Arbeitsspeicher" - -msgid "Disk space" -msgstr "Festplattenkapazität" - -msgid "Subtotal" -msgstr "Zwischensumme" - -msgid "VAT" -msgstr "Mehrwertsteuer" - -msgid "Discount" -msgstr "Rabatt" - -msgid "Total" -msgstr "Gesamt" - #, python-format msgid "" "By clicking \"Place order\" this plan will charge your credit card account " @@ -410,9 +480,6 @@ msgstr "" msgid "Place order" msgstr "Bestellen" -msgid "BACK TO LIST" -msgstr "ZURÜCK ZUR LISTE" - msgid "Processing..." msgstr "Abarbeitung..." @@ -425,24 +492,9 @@ msgstr "" msgid "Close" msgstr "Schliessen" -msgid "Some problem encountered. Please try again later." -msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal." - msgid "Order Nr." msgstr "Bestellung Nr." -msgid "Amount" -msgstr "Betrag" - -msgid "See Invoice" -msgstr "Siehe Rechnung" - -msgid "Page" -msgstr "" - -msgid "of" -msgstr "" - msgid "Your Order" msgstr "Deine Bestellung" @@ -539,9 +591,6 @@ msgstr "" "Wir nutzen Stripe für " "die Bezahlung und speichern keine Informationen in unserer Datenbank." -msgid "Add your public SSH key" -msgstr "Füge deinen öffentlichen SSH-Key hinzu" - msgid "Use your created key to access to the VM" msgstr "Benutze deinen erstellten SSH-Key um auf deine VM zugreifen zu können" @@ -783,6 +832,9 @@ msgstr "" msgid "Invalid number of cores" msgstr "Ungültige Anzahle CPU-Kerne" +msgid "Invalid calculator properties" +msgstr "" + msgid "Invalid RAM size" msgstr "Ungültige RAM-Grösse" @@ -821,6 +873,9 @@ msgstr "" "Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es " "noch einmal." +#~ msgid "Add your public SSH key" +#~ msgstr "Füge deinen öffentlichen SSH-Key hinzu" + #~ msgid "Do you want to cancel your Virtual Machine" #~ msgstr "Bist Du sicher, dass Du Deine virtuelle Maschine beenden willst" @@ -830,9 +885,6 @@ msgstr "" #~ msgid "My VM page" #~ msgstr "Meine VM page" -#~ msgid "Invoice Date" -#~ msgstr "Rechnung Datum" - #~ msgid "VM %(VM_ID)s terminated successfully" #~ msgstr "VM %(VM_ID)s erfolgreich beendet" From 39699da8eeea1cc811c66449a3a0a1728f9ac2df Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 9 Jul 2019 21:34:00 +0530 Subject: [PATCH 095/626] Update Changelog for 2.6.1 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index a9916173..63c59e11 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.6.1: 2019-07-09 + * #6941: [hosting dashboard] Show the card's expiry year & month too in the list of added cards (MR!710) 2.6: 2019-07-03 * #5509: Getting rid of our key by still supporting multiple user keys (MR!709) 2.5.11: 2019-06-11 From b50a543148d63d1f56c410c25210421a8e0e4b1b Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 22 Aug 2019 08:38:19 +0530 Subject: [PATCH 096/626] Remove public- prefix shown in django/node/rails hosting pages --- hosting/templates/hosting/includes/_pricing.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/includes/_pricing.html b/hosting/templates/hosting/includes/_pricing.html index 8abbf26f..41a5fcf2 100644 --- a/hosting/templates/hosting/includes/_pricing.html +++ b/hosting/templates/hosting/includes/_pricing.html @@ -57,7 +57,7 @@ From 7bc2c8eebef6407a827227ceb2164c552519d44e Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 22 Aug 2019 08:40:14 +0530 Subject: [PATCH 097/626] Update Changelog for 2.6.2 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 63c59e11..395691aa 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.6.2: 2019-08-22 + * #7068: [django/node/rails] Remove public- prefix from OS template names (MR!711) 2.6.1: 2019-07-09 * #6941: [hosting dashboard] Show the card's expiry year & month too in the list of added cards (MR!710) 2.6: 2019-07-03 From 97d83abffe7a21729a56bd954c6f6ea3f0b83520 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 26 Aug 2019 16:01:34 +0530 Subject: [PATCH 098/626] Comment out code that denied adding the same key --- hosting/forms.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/hosting/forms.py b/hosting/forms.py index 1bc99b8f..343ead28 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -9,7 +9,6 @@ from django.contrib.auth import authenticate from django.utils.translation import ugettext_lazy as _ from membership.models import CustomUser -from utils.hosting_utils import get_all_public_keys from .models import UserHostingKey, GenericProduct logger = logging.getLogger(__name__) @@ -193,14 +192,14 @@ class UserHostingKeyForm(forms.ModelForm): KEY_ERROR_MESSAGE = _("Please input a proper SSH key") openssh_pubkey_str = self.data.get('public_key').strip() - if openssh_pubkey_str in get_all_public_keys(self.request.user): - key_name = UserHostingKey.objects.filter( - user_id=self.request.user.id, - public_key=openssh_pubkey_str).first().name - KEY_EXISTS_MESSAGE = _( - "This key exists already with the name \"%(name)s\"") % { - 'name': key_name} - raise forms.ValidationError(KEY_EXISTS_MESSAGE) + # if openssh_pubkey_str in get_all_public_keys(self.request.user): + # key_name = UserHostingKey.objects.filter( + # user_id=self.request.user.id, + # public_key=openssh_pubkey_str).first().name + # KEY_EXISTS_MESSAGE = _( + # "This key exists already with the name \"%(name)s\"") % { + # 'name': key_name} + # raise forms.ValidationError(KEY_EXISTS_MESSAGE) with tempfile.NamedTemporaryFile(delete=True) as tmp_public_key_file: tmp_public_key_file.write(openssh_pubkey_str.encode('utf-8')) From 8b8c93d23ebecca33623971eff1bed574a685a49 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 26 Aug 2019 16:02:08 +0530 Subject: [PATCH 099/626] Filter distinct public keys only --- 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 ec97a320..b3c47e6e 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -18,7 +18,7 @@ def get_all_public_keys(customer): :return: A list of public keys """ return UserHostingKey.objects.filter(user_id=customer.id).values_list( - "public_key", flat=True) + "public_key", flat=True).distinct() def get_or_create_vm_detail(user, manager, vm_id): From 7684687dbcac662b82c723dd5d583dfab491a1c7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 26 Aug 2019 16:13:31 +0530 Subject: [PATCH 100/626] Remove commented code --- hosting/forms.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/hosting/forms.py b/hosting/forms.py index 343ead28..d98d258f 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -192,15 +192,6 @@ class UserHostingKeyForm(forms.ModelForm): KEY_ERROR_MESSAGE = _("Please input a proper SSH key") openssh_pubkey_str = self.data.get('public_key').strip() - # if openssh_pubkey_str in get_all_public_keys(self.request.user): - # key_name = UserHostingKey.objects.filter( - # user_id=self.request.user.id, - # public_key=openssh_pubkey_str).first().name - # KEY_EXISTS_MESSAGE = _( - # "This key exists already with the name \"%(name)s\"") % { - # 'name': key_name} - # raise forms.ValidationError(KEY_EXISTS_MESSAGE) - with tempfile.NamedTemporaryFile(delete=True) as tmp_public_key_file: tmp_public_key_file.write(openssh_pubkey_str.encode('utf-8')) tmp_public_key_file.flush() From b2d597232c445a73e0d7bcd6a6a52bde9e1f48ff Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 27 Aug 2019 11:13:31 +0530 Subject: [PATCH 101/626] Use opennebula user credentials to find if vm belongs to user --- hosting/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 87cb948c..6d023437 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1731,7 +1731,8 @@ class CheckUserVM(APIView): response = check_otp(user, realm, token) if response != 200: return Response('Invalid token', 403) - manager = OpenNebulaManager() + manager = OpenNebulaManager(settings.OPENNEBULA_USERNAME, + settings.OPENNEBULA_PASSWORD) # not the best way to lookup vms by ip # TODO: make this optimal vms = manager.get_vms() From dbd6685c4342d546d722d8100952ce270832c779 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 27 Aug 2019 14:34:26 +0530 Subject: [PATCH 102/626] Update Changelog --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 395691aa..df1e1699 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +Next: + * #7070: [check_vm/api] Bugfix: Provide oneadmin credentials to check whether a user is the owner of a VM (MR!713) 2.6.2: 2019-08-22 * #7068: [django/node/rails] Remove public- prefix from OS template names (MR!711) 2.6.1: 2019-07-09 From 41692b192973135c9f4e18b07f65c0d54aa88a7b Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 28 Aug 2019 22:13:15 +0530 Subject: [PATCH 103/626] Update Changelog for 2.6.3 --- Changelog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Changelog b/Changelog index df1e1699..1608db2b 100644 --- a/Changelog +++ b/Changelog @@ -1,4 +1,5 @@ -Next: +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) 2.6.2: 2019-08-22 * #7068: [django/node/rails] Remove public- prefix from OS template names (MR!711) From e4e074ea8d6bac85cdbc760d61d6422900b2a196 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 15 Sep 2019 09:12:36 +0530 Subject: [PATCH 104/626] Add explanatory text indicating puffy username on OpenBSD VMs --- hosting/locale/de/LC_MESSAGES/django.po | 16 +++++++++++----- .../templates/hosting/emails/new_booked_vm.html | 5 +++++ .../templates/hosting/emails/new_booked_vm.txt | 3 +++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/hosting/locale/de/LC_MESSAGES/django.po b/hosting/locale/de/LC_MESSAGES/django.po index 14d48da9..5c719457 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-07-09 15:21+0000\n" +"POT-Creation-Date: 2019-09-15 03:39+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -72,10 +72,6 @@ msgstr "Key-Name" msgid "Please input a proper SSH key" msgstr "Bitte verwende einen gültigen SSH-Key" -#, python-format -msgid "This key exists already with the name \"%(name)s\"" -msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits" - msgid "Comma not accepted in the name of the key" msgstr "" @@ -242,6 +238,9 @@ msgstr "" 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." + msgid "View Detail" msgstr "Details anzeigen" @@ -255,6 +254,9 @@ msgstr "Deine Bestellung von %(vm_name)s wurde entgegengenommen." msgid "You can view your VM detail by following the link below." msgstr "Um die Rechnung zu sehen, klicke auf den Link 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." + msgid "Password Reset" msgstr "Passwort zurücksetzen" @@ -873,6 +875,10 @@ msgstr "" "Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es " "noch einmal." +#, python-format +#~ msgid "This key exists already with the name \"%(name)s\"" +#~ msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits" + #~ msgid "Add your public SSH key" #~ msgstr "Füge deinen öffentlichen SSH-Key hinzu" diff --git a/hosting/templates/hosting/emails/new_booked_vm.html b/hosting/templates/hosting/emails/new_booked_vm.html index 7bc0cf3a..9fad05fd 100644 --- a/hosting/templates/hosting/emails/new_booked_vm.html +++ b/hosting/templates/hosting/emails/new_booked_vm.html @@ -33,6 +33,11 @@

{% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %}

+ {% if 'OpenBSD' in vm_name %} +

+ {% blocktrans %}You can log in to your VM by the username puffy.{% endblocktrans %} +

+ {% endif %} diff --git a/hosting/templates/hosting/emails/new_booked_vm.txt b/hosting/templates/hosting/emails/new_booked_vm.txt index cfd9c63a..42b48e6a 100644 --- a/hosting/templates/hosting/emails/new_booked_vm.txt +++ b/hosting/templates/hosting/emails/new_booked_vm.txt @@ -5,6 +5,9 @@ {% blocktrans %}You have ordered a new virtual machine!{% endblocktrans %} {% blocktrans %}Your order of {{vm_name}} has been charged.{% endblocktrans %} {% blocktrans %}You can view your VM detail by following the link below.{% endblocktrans %} +{% if 'OpenBSD' in vm_name %} + {% blocktrans %}You can log in to your VM by the username puffy.{% endblocktrans %} +{% endif %} {{ base_url }}{{ order_url }} From 4dd49051b4165fb0fe3b3bbdb63acfc1180ae576 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 15 Sep 2019 09:38:36 +0530 Subject: [PATCH 105/626] 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 106/626] 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 107/626] 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 108/626] 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 109/626] 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 @@ {% for line_item in line_items %} - + {% endfor %} - +
ProductPeriodQtyUnit PriceTotal
{% 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}}
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 110/626] 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 111/626] 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 112/626] 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 113/626] 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 114/626] 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 115/626] 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 116/626] 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 117/626] 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 118/626] 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 119/626] 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 120/626] 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 121/626] 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 122/626] 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 123/626] 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 124/626] 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 125/626] 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 126/626] 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 127/626] 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 128/626] 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 129/626] 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 130/626] 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 131/626] 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 132/626] 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 133/626] 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 134/626] 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 135/626] 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 136/626] 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 137/626] 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 138/626] 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 139/626] 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 140/626] 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 141/626] 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 142/626] 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 143/626] 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 144/626] 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 145/626] 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 146/626] 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 147/626] 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 148/626] 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 149/626] 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 150/626] 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 151/626] 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 152/626] 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 153/626] 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 154/626] 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 155/626] 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 156/626] 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 157/626] 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 158/626] 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 159/626] 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 160/626] 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 161/626] 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 162/626] 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 163/626] 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 164/626] 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 165/626] 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 166/626] 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 167/626] 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 168/626] 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 169/626] 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 170/626] 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 171/626] 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 172/626] 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 173/626] 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 174/626] 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 175/626] 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 176/626] 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 177/626] 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 178/626] 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 179/626] 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 180/626] 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 181/626] 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 182/626] 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 183/626] 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 184/626] 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 185/626] 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 186/626] 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 187/626] 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 188/626] 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 189/626] 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 190/626] 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 191/626] 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 192/626] 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 193/626] 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 194/626] 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 195/626] 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 196/626] 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 197/626] 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 198/626] Show username in navbar and setting. Show greeting in dashboard for user's name --- hosting/static/hosting/css/dashboard.css | 1 - hosting/static/hosting/css/virtual-machine.css | 11 +++++++++++ hosting/templates/hosting/dashboard.html | 3 +++ hosting/templates/hosting/includes/_navbar_user.html | 2 +- hosting/templates/hosting/settings.html | 4 ++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/hosting/static/hosting/css/dashboard.css b/hosting/static/hosting/css/dashboard.css index c7bbecd9..0b718178 100644 --- a/hosting/static/hosting/css/dashboard.css +++ b/hosting/static/hosting/css/dashboard.css @@ -23,7 +23,6 @@ .hosting-dashboard .dashboard-container-head { color: #fff; - margin-bottom: 60px; } .hosting-dashboard-item { diff --git a/hosting/static/hosting/css/virtual-machine.css b/hosting/static/hosting/css/virtual-machine.css index 726b0f35..4d490ff7 100644 --- a/hosting/static/hosting/css/virtual-machine.css +++ b/hosting/static/hosting/css/virtual-machine.css @@ -248,6 +248,9 @@ .dashboard-title-thin { font-size: 22px; } + .dashboard-greetings-thin { + font-size: 16px; + } } .btn-vm-invoice { @@ -315,6 +318,11 @@ font-size: 32px; } +.dashboard-greetings-thin { + font-weight: 300; + font-size: 24px; +} + .dashboard-title-thin .un-icon { height: 34px; margin-right: 5px; @@ -411,6 +419,9 @@ .dashboard-title-thin { font-size: 22px; } + .dashboard-greetings-thin { + font-size: 16px; + } .dashboard-title-thin .un-icon { height: 22px; width: 22px; diff --git a/hosting/templates/hosting/dashboard.html b/hosting/templates/hosting/dashboard.html index 35ee9b6e..f87c3f61 100644 --- a/hosting/templates/hosting/dashboard.html +++ b/hosting/templates/hosting/dashboard.html @@ -7,6 +7,9 @@

{% trans "My Dashboard" %}

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

{% trans "Create VM" %}

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

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

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


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

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

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

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

- {% endif %} -

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

-

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

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

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

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

+
+
+
+
+
+ +
+
+

+
+
+

Pre VAT

+
+
+

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

+
+
+
+
+

Price

+
+
+

{{vm.price|intcomma}} CHF

+
+
+

{{vm.price_with_vat|intcomma}} CHF

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

{{vm.discount.name}}

+
+
+

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

+
+
+

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

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

Total

+
+
+

{{vm.price_after_discount|intcomma}} CHF

+
+
+

{{vm.price_after_discount_with_vat|intcomma}} CHF

+
+
+
+
+
+
+
+

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

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

-
+

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


-
+

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


-
+
-
+

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


-
+

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


-
- +
-
+

-
-

Pre VAT

+
+

{% trans "Pre VAT" %}

-
-

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

+
+

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

-
+

Price

-
+

{{vm.price|intcomma}} CHF

-
+

{{vm.price_with_vat|intcomma}} CHF

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

{{vm.discount.name}}

-
+

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

-
+

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

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

-
+
-
+

Total

-
+

{{vm.price_after_discount|intcomma}} CHF

-
+

{{vm.price_after_discount_with_vat|intcomma}} CHF

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

-
-

+

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

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

- +

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


{% trans "Order summary" %}

- {% if generic_payment_details %} -

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

-
-
- {% if generic_payment_details.vat_rate > 0 %} -

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

-

- {% trans "VAT for" %} {{generic_payment_details.vat_country}} ({{generic_payment_details.vat_rate}}%) : - CHF {{generic_payment_details.vat_amount|floatformat:2|intcomma}} -

-

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

- {% else %} -

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

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

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

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

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

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

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

- + + {% if generic_payment_details %} +

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

+ {% if generic_payment_details.description %} +

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

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

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

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

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

+
+
+
+
+
+
+
+

+
+
+

{% trans "Pre VAT" %}

+
+
+

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

+
+
+
+
+

Price

+
+
+

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

+
+
+

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

+
+
+
+
+
+
+
+
+
+

Total

+
+
+

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

+
+
+

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

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

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

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

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

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

+
+
+
+
+
+
+
+

+
+
+

{% trans "Pre VAT" %}

+
+
+

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

+
-
-

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

+
+
+

Price

+
+
+

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

+
+
+

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

+
-
-
-
-
-
-
-

-
-
-

{% trans "Pre VAT" %}

-
-
-

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

-
-
-
-
-

Price

-
-
-

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

-
-
-

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

-
-
-
-
-
-
-
-
-
-

Total

-
-
-

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

-
-
-

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

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

Total

+
+
+

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

+
+
+

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

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

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

+

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

{% endif %} +

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

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

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

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

Price

-

{{vm.price|intcomma}} CHF

+

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

-

{{vm.price_with_vat|intcomma}} CHF

+

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

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

{{vm.discount.name}}

-

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

+

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

-

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

+

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

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

Total

-

{{vm.price_after_discount|intcomma}} CHF

+

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

-

{{vm.price_after_discount_with_vat|intcomma}} CHF

+

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

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

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


-
+

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


-
+

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

-
+

Total

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

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

-
+

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


-
+

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


-
+

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

-
+

Total

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

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