diff --git a/datacenterlight/management/commands/deleteuser.py b/datacenterlight/management/commands/deleteuser.py new file mode 100644 index 00000000..cc1cd477 --- /dev/null +++ b/datacenterlight/management/commands/deleteuser.py @@ -0,0 +1,216 @@ +import logging +import oca +import sys +import stripe + +from django.core.management.base import BaseCommand +from membership.models import CustomUser, DeletedUser +from hosting.models import ( + HostingOrder, HostingBill, VMDetail, UserCardDetail, UserHostingKey +) +from opennebula_api.models import OpenNebulaManager +logger = logging.getLogger(__name__) + + +def query_yes_no(question, default="yes"): + """Ask a yes/no question via raw_input() and return their answer. + + "question" is a string that is presented to the user. + "default" is the presumed answer if the user just hits . + It must be "yes" (the default), "no" or None (meaning + an answer is required of the user). + + The "answer" return value is True for "yes" or False for "no". + """ + valid = {"yes": True, "y": True, "ye": True, + "no": False, "n": False} + if default is None: + prompt = " [y/n] " + elif default == "yes": + prompt = " [Y/n] " + elif default == "no": + prompt = " [y/N] " + else: + raise ValueError("invalid default answer: '%s'" % default) + + while True: + sys.stdout.write(question + prompt) + choice = input().lower() + if default is not None and choice == '': + return valid[default] + elif choice in valid: + return valid[choice] + else: + sys.stdout.write("Please respond with 'yes' or 'no' " + "(or 'y' or 'n').\n") + + +class Command(BaseCommand): + help = '''Deletes all resources of the user from the project''' + + 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']: + r = query_yes_no("Are you sure you want to delete {} ?".format( + email, None + )) + if r: + logger.debug("Deleting user {}".format(email)) + # Get stripe customer instance and delete the customer + 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) + stripe_customer = cus_user.stripecustomer + c = stripe.Customer.retrieve( + stripe_customer.stripe_id + ) + cus_delete_obj = c.delete() + if cus_delete_obj.deleted: + logger.debug( + "StripeCustomer {} associated with {} deleted" + "".format(stripe_customer.stripe_id, email) + ) + 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: + order.billing_address.delete() + logger.debug( + "Billing Address {} associated with {} deleted" + "".format(order.billing_address.id, email) + ) + else: + logger.error( + "Error while deleting the billing_address") + + # Delete Order Detail + if order.order_detail is not None: + order.order_detail.delete() + logger.debug( + "Order Detail {} associated with {} deleted" + "".format(order.order_detail.id, email) + ) + else: + logger.error( + "Error while deleting the order_detail. None") + + # Delete order + if order is not None: + order.delete() + logger.debug( + "Order {} associated with {} deleted" + "".format(order.id, email) + ) + 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: + bill.billing_address.delete() + logger.debug( + "HostingBills billing address {} associated with {} deleted" + "".format(bill.billing_address.id, email) + ) + else: + logger.error( + "Error while deleting the HostingBill's Billing address") + + if bill is not None: + bill.delete() + logger.debug( + "HostingBill {} associated with {} deleted" + "".format(bill.id, email) + ) + 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: + vm_detail.delete() + logger.debug( + "vm_detail {} associated with {} deleted" + "".format(vm_detail.id, email) + ) + else: + logger.error( + "Error while deleting the vm_detail") + + # delete UserCardDetail + ucds = UserCardDetail.objects.filter( + stripe_customer=stripe_customer + ) + for ucd in ucds: + if ucd is not None: + ucd.delete() + logger.debug( + "User Card Detail {} associated with {} deleted" + "".format(ucd.id, email) + ) + else: + logger.error( + "Error while deleting the User Card Detail") + + # delete UserHostingKey + uhks = UserHostingKey.objects.filter( + user=cus_user + ) + for uhk in uhks: + uhk.delete() + + # delete stripe customer + stripe_customer.delete() + + # add user to deleteduser + DeletedUser.objects.create( + email=cus_user.email, name=cus_user.name, + user_id = cus_user.id + ) + + # delete CustomUser + cus_user.delete() + + # remove user from OpenNebula + manager = OpenNebulaManager() + user_pool = manager._get_user_pool() + on_user = user_pool.get_by_name(email) + if on_user.id > 0: + logger.debug( + "Deleting user {} => ID={} from opennebula".format( + email, on_user.id) + ) + manager.oneadmin_client.call( + oca.User.METHODS['delete'], on_user.id + ) + else: + logger.error( + "User not found with email {}. " + "Not doing anything".format(email) + ) + + logger.debug("Deleted {} SUCCESSFULLY.".format(email)) + except Exception as e: + print(" *** Error occurred. Details {}".format(str(e))) diff --git a/hosting/models.py b/hosting/models.py index d2011654..616b036f 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -212,6 +212,12 @@ class UserHostingKey(models.Model): # self.save(update_fields=['public_key']) return private_key, public_key + def delete(self,*args,**kwargs): + if os.path.isfile(self.private_key.path): + os.remove(self.private_key.path) + + super(UserHostingKey, self).delete(*args,**kwargs) + class HostingBill(AssignPermissionsMixin, models.Model): customer = models.ForeignKey(StripeCustomer) diff --git a/membership/migrations/0008_change_user_id_to_customer_id_in_djangocms_blog.py b/membership/migrations/0008_change_user_id_to_customer_id_in_djangocms_blog.py new file mode 100644 index 00000000..4a8530e9 --- /dev/null +++ b/membership/migrations/0008_change_user_id_to_customer_id_in_djangocms_blog.py @@ -0,0 +1,13 @@ +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [('membership', '0007_auto_20180213_0128'), + ('djangocms_blog', '0032_auto_20180109_0023'), + ] + + operations = [ + migrations.RunSQL( + "ALTER TABLE djangocms_blog_authorentriesplugin_authors " + "RENAME COLUMN user_id TO customuser_id;"), + ] diff --git a/membership/migrations/0009_deleteduser.py b/membership/migrations/0009_deleteduser.py new file mode 100644 index 00000000..146d3847 --- /dev/null +++ b/membership/migrations/0009_deleteduser.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-05-06 06:06 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0008_change_user_id_to_customer_id_in_djangocms_blog'), + ] + + operations = [ + migrations.CreateModel( + name='DeletedUser', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user_id', models.PositiveIntegerField()), + ('name', models.CharField(max_length=254)), + ('email', models.EmailField(max_length=254, unique=True)), + ('deleted_at', models.DateTimeField(auto_now_add=True)), + ], + ), + ] diff --git a/membership/models.py b/membership/models.py index c5e83735..1a622bd5 100644 --- a/membership/models.py +++ b/membership/models.py @@ -265,6 +265,15 @@ class CreditCards(models.Model): pass +class DeletedUser(models.Model): + user_id = models.PositiveIntegerField() + + # why 254 ? => to be consistent with legacy code + name = models.CharField(max_length=254) + email = models.EmailField(unique=True, max_length=254) + deleted_at = models.DateTimeField(auto_now_add=True) + + class Calendar(models.Model): datebooked = models.DateField() user = models.ForeignKey(CustomUser)