From 8df1d8dc7ca2d8d52d03a20e80736c69d3df7e29 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sun, 9 Aug 2020 14:38:10 +0200 Subject: [PATCH] begin refactor product to user orders instead of single order Signed-off-by: Nico Schottelius --- .../migrations/0004_auto_20200809_1237.py | 23 ++++++ .../migrations/0004_auto_20200809_1237.py | 23 ++++++ .../commands/add-opennebula-vm-orders.py | 59 +++++++-------- .../migrations/0013_auto_20200809_1237.py | 40 ++++++++++ uncloud_pay/models.py | 74 +++++++++---------- uncloud_pay/templates/bill.html.j2 | 4 +- uncloud_pay/tests.py | 4 +- .../migrations/0004_auto_20200809_1237.py | 32 ++++++++ .../migrations/0004_auto_20200809_1237.py | 41 ++++++++++ uncloud_vm/models.py | 14 +--- 10 files changed, 224 insertions(+), 90 deletions(-) create mode 100644 opennebula/migrations/0004_auto_20200809_1237.py create mode 100644 uncloud_net/migrations/0004_auto_20200809_1237.py create mode 100644 uncloud_pay/migrations/0013_auto_20200809_1237.py create mode 100644 uncloud_service/migrations/0004_auto_20200809_1237.py create mode 100644 uncloud_vm/migrations/0004_auto_20200809_1237.py diff --git a/opennebula/migrations/0004_auto_20200809_1237.py b/opennebula/migrations/0004_auto_20200809_1237.py new file mode 100644 index 0000000..ac4ac86 --- /dev/null +++ b/opennebula/migrations/0004_auto_20200809_1237.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1 on 2020-08-09 12:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0013_auto_20200809_1237'), + ('opennebula', '0003_auto_20200808_1953'), + ] + + operations = [ + migrations.RemoveField( + model_name='vm', + name='order', + ), + migrations.AddField( + model_name='vm', + name='orders', + field=models.ManyToManyField(to='uncloud_pay.Order'), + ), + ] diff --git a/uncloud_net/migrations/0004_auto_20200809_1237.py b/uncloud_net/migrations/0004_auto_20200809_1237.py new file mode 100644 index 0000000..7d500c2 --- /dev/null +++ b/uncloud_net/migrations/0004_auto_20200809_1237.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1 on 2020-08-09 12:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0013_auto_20200809_1237'), + ('uncloud_net', '0003_auto_20200808_1953'), + ] + + operations = [ + migrations.RemoveField( + model_name='vpnnetwork', + name='order', + ), + migrations.AddField( + model_name='vpnnetwork', + name='orders', + field=models.ManyToManyField(to='uncloud_pay.Order'), + ), + ] diff --git a/uncloud_pay/management/commands/add-opennebula-vm-orders.py b/uncloud_pay/management/commands/add-opennebula-vm-orders.py index 6ca1b63..1d66790 100644 --- a/uncloud_pay/management/commands/add-opennebula-vm-orders.py +++ b/uncloud_pay/management/commands/add-opennebula-vm-orders.py @@ -49,15 +49,12 @@ class Command(BaseCommand): } ) - # 25206 + SSD - vm25206 = VMProduct(name="25206", cores=1, ram_in_gb=4, owner=user) + vm25206 = VMProduct.objects.create(name="one-25206", cores=1, ram_in_gb=4, owner=user) vm25206.create_order_at(timezone.make_aware(datetime.datetime(2020,3,3))) - vm25206.save() - vm25206_ssd = VMDiskProduct(vm=vm25206, owner=user, size_in_gb=30) - vm25206_ssd.create_order_at(timezone.make_aware(datetime.datetime(2020,3,3))) - vm25206_ssd.save() + # vm25206_ssd = VMDiskProduct.objects.create(vm=vm25206, owner=user, size_in_gb=30) + # vm25206_ssd.create_order_at(timezone.make_aware(datetime.datetime(2020,3,3))) # change 1 vm25206.cores = 2 @@ -65,55 +62,53 @@ class Command(BaseCommand): vm25206.save() vm25206.create_or_update_order(when_to_start=timezone.make_aware(datetime.datetime(2020,4,17))) + sys.exit(0) + # change 2 - vm25206_ssd.size_in_gb = 50 - vm25206_ssd.save() - vm25206_ssd.create_or_update_order(when_to_start=timezone.make_aware(datetime.datetime(2020,8,5))) + # vm25206_ssd.size_in_gb = 50 + # vm25206_ssd.save() + # vm25206_ssd.create_or_update_order(when_to_start=timezone.make_aware(datetime.datetime(2020,8,5))) # 25206 done. # 25615 - vm25615 = VMProduct(name="25615", cores=1, ram_in_gb=4, owner=user) + vm25615 = VMProduct.objects.create(name="one-25615", cores=1, ram_in_gb=4, owner=user) vm25615.create_order_at(timezone.make_aware(datetime.datetime(2020,3,3))) - vm25615.save() - - vm25615_ssd = VMDiskProduct(vm=vm25615, owner=user, size_in_gb=30) - vm25615_ssd.create_order_at(timezone.make_aware(datetime.datetime(2020,3,3))) - vm25615_ssd.save() - - - Bill.create_next_bill_for_user(user) - - sys.exit(0) - - - - + # Change 2020-04-17 vm25615.cores = 2 vm25615.ram_in_gb = 8 vm25615.save() vm25615.create_or_update_order(when_to_start=timezone.make_aware(datetime.datetime(2020,4,17))) + # vm25615_ssd = VMDiskProduct(vm=vm25615, owner=user, size_in_gb=30) + # vm25615_ssd.create_order_at(timezone.make_aware(datetime.datetime(2020,3,3))) + # vm25615_ssd.save() + + vm25208 = VMProduct.objects.create(name="one-25208", cores=1, ram_in_gb=4, owner=user) + vm25208.create_order_at(timezone.make_aware(datetime.datetime(2020,3,5))) + + vm25208.cores = 2 + vm25208.ram_in_gb = 8 + vm25208.save() + vm25208.create_or_update_order(when_to_start=timezone.make_aware(datetime.datetime(2020,4,17))) + + Bill.create_next_bills_for_user(user, ending_date=end_of_month(timezone.make_aware(datetime.datetime(2020,7,31)))) + + sys.exit(0) + + vm25615_ssd.size_in_gb = 50 vm25615_ssd.save() vm25615_ssd.create_or_update_order(when_to_start=timezone.make_aware(datetime.datetime(2020,8,5))) - vm25208 = VMProduct.objects.create(name="OpenNebula 25208", - cores=1, - ram_in_gb=4, - owner=user) vm25208_ssd = VMDiskProduct.objects.create(vm=vm25208, owner=user, size_in_gb=30) - vm25208.cores = 2 - vm25208.ram_in_gb = 8 - vm25208.save() - vm25208.create_or_update_order(when_to_start=timezone.make_aware(datetime.datetime(2020,4,17))) vm25208_ssd.size_in_gb = 50 vm25208_ssd.save() diff --git a/uncloud_pay/migrations/0013_auto_20200809_1237.py b/uncloud_pay/migrations/0013_auto_20200809_1237.py new file mode 100644 index 0000000..7f1ed91 --- /dev/null +++ b/uncloud_pay/migrations/0013_auto_20200809_1237.py @@ -0,0 +1,40 @@ +# Generated by Django 3.1 on 2020-08-09 12:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0012_auto_20200809_1026'), + ] + + operations = [ + migrations.RemoveField( + model_name='sampleonetimeproduct', + name='order', + ), + migrations.RemoveField( + model_name='samplerecurringproduct', + name='order', + ), + migrations.RemoveField( + model_name='samplerecurringproductonetimefee', + name='order', + ), + migrations.AddField( + model_name='sampleonetimeproduct', + name='orders', + field=models.ManyToManyField(to='uncloud_pay.Order'), + ), + migrations.AddField( + model_name='samplerecurringproduct', + name='orders', + field=models.ManyToManyField(to='uncloud_pay.Order'), + ), + migrations.AddField( + model_name='samplerecurringproductonetimefee', + name='orders', + field=models.ManyToManyField(to='uncloud_pay.Order'), + ), + ] diff --git a/uncloud_pay/models.py b/uncloud_pay/models.py index 5d69523..1eb2071 100644 --- a/uncloud_pay/models.py +++ b/uncloud_pay/models.py @@ -53,6 +53,10 @@ def end_of_this_month(): _, last_day = monthrange(a_day.year, a_day.month) return a_day.replace(day=last_day,hour=23,minute=59,second=59, microsecond=0) +def end_before(a_date): + """ Return suitable datetimefield for ending just before a_date """ + return a_date - datetime.timedelta(seconds=1) + def default_payment_delay(): return timezone.now() + BILL_PAYMENT_DELAY @@ -363,30 +367,7 @@ class Order(models.Model): super().save(*args, **kwargs) def __str__(self): - return f"{self.description} (order {self.id})" - - - # def active_before(self, ending_date): - # # Was this order started before the specified ending date? - # if self.starting_date <= ending_date: - # if self.ending_date: - # if self.ending_date > ending_date: - # pass - - - # Termination needs to be verified, maybe also include checking depending orders - # @property - # def is_terminated_now(self): - # return self.is_terminated_at(timezone.now()) - - # def is_terminated_at(self, a_date): - # return self.ending_date != None and self.ending_date <= a_date - - # def terminate(self): - # if not self.is_terminated: - # self.ending_date = timezone.now() - # self.save() - + return f"{self.description} (order={self.id})" class Bill(models.Model): """ @@ -433,6 +414,14 @@ class Bill(models.Model): bill_records = BillRecord.objects.filter(bill=self) return sum([ br.sum for br in bill_records ]) + @classmethod + def create_bills_for_all_users(cls): + """ + Create bills for all users + """ + + for owner in get_user_model().objects.all(): + cls.create_next_bills_for_user(owner) @classmethod def create_next_bills_for_user(cls, owner, ending_date=None): @@ -578,14 +567,6 @@ class Bill(models.Model): return bill - @classmethod - def create_all_bills(cls): - for owner in get_user_model().objects.all(): - # mintime = time of first order - # maxtime = time of last order - # iterate month based through it - - cls.create_next_bill_for_user(owner) class BillRecord(models.Model): """ @@ -633,10 +614,19 @@ class Product(UncloudModel): description = "Generic Product" - order = models.ForeignKey(Order, - on_delete=models.CASCADE, - editable=True, - null=True) + orders = models.ManyToManyField(Order) + # one_time_order = models.ForeignKey(Order, + # on_delete=models.CASCADE, + # editable=True, + # null=True, + # related_name='product_one_time') + + # recurring_order = models.ForeignKey(Order, + # on_delete=models.CASCADE, + # editable=True, + # null=True, + # related_name='product_recurring') + # FIXME: editable=True -> is in the admin, but also editable in DRF status = models.CharField(max_length=32, @@ -691,12 +681,10 @@ class Product(UncloudModel): if not when_to_start: when_to_start = timezone.now() - if not self.order: - self.create_order_at(when_to_start, recurring_period) - - else: + # Update order = create new order + if self.order: previous_order = self.order - when_to_end = when_to_start - datetime.timedelta(seconds=1) + when_to_end = end_before(when_to_start) new_order = Order.objects.create(owner=self.owner, billing_address=self.order.billing_address, @@ -706,11 +694,15 @@ class Product(UncloudModel): description=str(self), replaces=self.order) + print(new_order) self.order.end_date = when_to_end self.order.save() self.order = new_order + else: + return self.create_order_at(when_to_start, recurring_period) + @property def recurring_price(self): """ implement correct values in the child class """ diff --git a/uncloud_pay/templates/bill.html.j2 b/uncloud_pay/templates/bill.html.j2 index d5993ae..5baa7ca 100644 --- a/uncloud_pay/templates/bill.html.j2 +++ b/uncloud_pay/templates/bill.html.j2 @@ -709,10 +709,8 @@ oAsAAAAAAACGQNAFAAAAAAAAQyDoAgAAAAAAgCEQdAEAAAAAAMAQCLoAAAAAAABgCP83AL6WQ1Y7 {% for record in bill_records %} {{ record.starting_date|date:"c" }} - {% if record.ending_date %} - {{ record.ending_date|date:"c" }} - {% endif %} - {{ record.order.description }} + {{ record.order }} {{ record.quantity|floatformat:2 }} {{ record.order.price|floatformat:2 }} diff --git a/uncloud_pay/tests.py b/uncloud_pay/tests.py index 3ae8a65..dbbedf4 100644 --- a/uncloud_pay/tests.py +++ b/uncloud_pay/tests.py @@ -6,7 +6,7 @@ from .models import * from uncloud_service.models import GenericServiceProduct -class ProductOrderTestCase(TestCase): +class ProductTestCase(TestCase): """ Test products and products <-> order interaction """ @@ -168,7 +168,7 @@ class BillingAddressTestCase(TestCase): -class BillAndOrderTestCase(TestCase): +class BillTestCase(TestCase): def setUp(self): self.user_without_address = get_user_model().objects.create( username='no_home_person', diff --git a/uncloud_service/migrations/0004_auto_20200809_1237.py b/uncloud_service/migrations/0004_auto_20200809_1237.py new file mode 100644 index 0000000..743514f --- /dev/null +++ b/uncloud_service/migrations/0004_auto_20200809_1237.py @@ -0,0 +1,32 @@ +# Generated by Django 3.1 on 2020-08-09 12:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0013_auto_20200809_1237'), + ('uncloud_service', '0003_auto_20200808_1953'), + ] + + operations = [ + migrations.RemoveField( + model_name='genericserviceproduct', + name='order', + ), + migrations.RemoveField( + model_name='matrixserviceproduct', + name='order', + ), + migrations.AddField( + model_name='genericserviceproduct', + name='orders', + field=models.ManyToManyField(to='uncloud_pay.Order'), + ), + migrations.AddField( + model_name='matrixserviceproduct', + name='orders', + field=models.ManyToManyField(to='uncloud_pay.Order'), + ), + ] diff --git a/uncloud_vm/migrations/0004_auto_20200809_1237.py b/uncloud_vm/migrations/0004_auto_20200809_1237.py new file mode 100644 index 0000000..b89a920 --- /dev/null +++ b/uncloud_vm/migrations/0004_auto_20200809_1237.py @@ -0,0 +1,41 @@ +# Generated by Django 3.1 on 2020-08-09 12:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('uncloud_pay', '0013_auto_20200809_1237'), + ('uncloud_vm', '0003_auto_20200808_1953'), + ] + + operations = [ + migrations.RemoveField( + model_name='vmdiskproduct', + name='order', + ), + migrations.RemoveField( + model_name='vmproduct', + name='order', + ), + migrations.RemoveField( + model_name='vmsnapshotproduct', + name='order', + ), + migrations.AddField( + model_name='vmdiskproduct', + name='orders', + field=models.ManyToManyField(to='uncloud_pay.Order'), + ), + migrations.AddField( + model_name='vmproduct', + name='orders', + field=models.ManyToManyField(to='uncloud_pay.Order'), + ), + migrations.AddField( + model_name='vmsnapshotproduct', + name='orders', + field=models.ManyToManyField(to='uncloud_pay.Order'), + ), + ] diff --git a/uncloud_vm/models.py b/uncloud_vm/models.py index dc2369e..a625555 100644 --- a/uncloud_vm/models.py +++ b/uncloud_vm/models.py @@ -58,13 +58,10 @@ class VMProduct(Product): VMCluster, on_delete=models.CASCADE, editable=False, blank=True, null=True ) - # VM-specific. The name is only intended for customers: it's a pain to - # remember IDs (speaking from experience as ungleich customer)! name = models.CharField(max_length=32, blank=True, null=True) cores = models.IntegerField() ram_in_gb = models.FloatField() - # Default recurring price is PER_MONTH, see uncloud_pay.models.Product. @property def recurring_price(self): return self.cores * 3 + self.ram_in_gb * 4 @@ -83,14 +80,7 @@ class VMProduct(Product): def __str__(self): - name = f"{self.id}" - - if self.name: - name = f"{self.id} ({self.name})" - - return "VM {}: {} cores {} gb ram".format(name, - self.cores, - self.ram_in_gb) + return f"VM id={self.id},name={self.name},cores={self.cores},ram_in_gb={self.ram_in_gb}" class VMWithOSProduct(VMProduct): @@ -165,7 +155,7 @@ class VMDiskProduct(Product): default=VMDiskType.CEPH_SSD) def __str__(self): - return f"Disk {self.size_in_gb}GB ({self.disk_type}) for VM '{self.vm.name}'" + return f"Disk {self.size_in_gb}GB ({self.disk_type}) for {self.vm}" @property def recurring_price(self):