forked from uncloud/uncloud
add sample products and improve testing for Product
This commit is contained in:
parent
6a928a2b2a
commit
0dd1093812
4 changed files with 156 additions and 17 deletions
65
uncloud_pay/migrations/0010_auto_20200809_0856.py
Normal file
65
uncloud_pay/migrations/0010_auto_20200809_0856.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
# Generated by Django 3.1 on 2020-08-09 08:56
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('uncloud_pay', '0009_auto_20200808_2113'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='depends_on',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_of', to='uncloud_pay.order'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='replaces',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replaced_by', to='uncloud_pay.order'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SampleRecurringProductOneTimeFee',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('extra_data', models.JSONField(blank=True, editable=False, null=True)),
|
||||||
|
('status', models.CharField(choices=[('PENDING', 'Pending'), ('AWAITING_PAYMENT', 'Awaiting payment'), ('BEING_CREATED', 'Being created'), ('SCHEDULED', 'Scheduled'), ('ACTIVE', 'Active'), ('MODIFYING', 'Modifying'), ('DELETED', 'Deleted'), ('DISABLED', 'Disabled'), ('UNUSABLE', 'Unusable')], default='AWAITING_PAYMENT', max_length=32)),
|
||||||
|
('order', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.order')),
|
||||||
|
('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SampleRecurringProduct',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('extra_data', models.JSONField(blank=True, editable=False, null=True)),
|
||||||
|
('status', models.CharField(choices=[('PENDING', 'Pending'), ('AWAITING_PAYMENT', 'Awaiting payment'), ('BEING_CREATED', 'Being created'), ('SCHEDULED', 'Scheduled'), ('ACTIVE', 'Active'), ('MODIFYING', 'Modifying'), ('DELETED', 'Deleted'), ('DISABLED', 'Disabled'), ('UNUSABLE', 'Unusable')], default='AWAITING_PAYMENT', max_length=32)),
|
||||||
|
('order', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.order')),
|
||||||
|
('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SampleOneTimeProduct',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('extra_data', models.JSONField(blank=True, editable=False, null=True)),
|
||||||
|
('status', models.CharField(choices=[('PENDING', 'Pending'), ('AWAITING_PAYMENT', 'Awaiting payment'), ('BEING_CREATED', 'Being created'), ('SCHEDULED', 'Scheduled'), ('ACTIVE', 'Active'), ('MODIFYING', 'Modifying'), ('DELETED', 'Deleted'), ('DISABLED', 'Disabled'), ('UNUSABLE', 'Unusable')], default='AWAITING_PAYMENT', max_length=32)),
|
||||||
|
('order', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.order')),
|
||||||
|
('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -596,7 +596,6 @@ class BillRecord(models.Model):
|
||||||
|
|
||||||
return record_delta.total_seconds()/self.order.recurring_period
|
return record_delta.total_seconds()/self.order.recurring_period
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sum(self):
|
def sum(self):
|
||||||
return self.order.price * Decimal(self.quantity)
|
return self.order.price * Decimal(self.quantity)
|
||||||
|
@ -604,6 +603,12 @@ class BillRecord(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.bill}: {self.quantity} x {self.order}"
|
return f"{self.bill}: {self.quantity} x {self.order}"
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.ending_date < self.starting_date:
|
||||||
|
raise ValidationError("End date cannot be before starting date")
|
||||||
|
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# Products
|
# Products
|
||||||
|
@ -703,13 +708,12 @@ class Product(UncloudModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def recurring_price(self):
|
def recurring_price(self):
|
||||||
pass # To be implemented in child.
|
""" implement correct values in the child class """
|
||||||
|
return 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def one_time_price(self):
|
def one_time_price(self):
|
||||||
"""
|
""" implement correct values in the child class """
|
||||||
Default is 0 CHF
|
|
||||||
"""
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -799,3 +803,30 @@ class Product(UncloudModel):
|
||||||
else:
|
else:
|
||||||
# FIXME: use the right type of exception here!
|
# FIXME: use the right type of exception here!
|
||||||
raise Exception("Did not implement the discounter for this case")
|
raise Exception("Did not implement the discounter for this case")
|
||||||
|
|
||||||
|
# Sample products included into uncloud
|
||||||
|
class SampleOneTimeProduct(Product):
|
||||||
|
|
||||||
|
default_recurring_period = RecurringPeriod.ONE_TIME
|
||||||
|
|
||||||
|
@property
|
||||||
|
def one_time_price(self):
|
||||||
|
return 5
|
||||||
|
|
||||||
|
class SampleRecurringProduct(Product):
|
||||||
|
default_recurring_period = RecurringPeriod.PER_30D
|
||||||
|
|
||||||
|
@property
|
||||||
|
def recurring_price(self):
|
||||||
|
return 10
|
||||||
|
|
||||||
|
class SampleRecurringProductOneTimeFee(Product):
|
||||||
|
default_recurring_period = RecurringPeriod.PER_30D
|
||||||
|
|
||||||
|
@property
|
||||||
|
def one_time_price(self):
|
||||||
|
return 5
|
||||||
|
|
||||||
|
@property
|
||||||
|
def recurring_price(self):
|
||||||
|
return 1
|
||||||
|
|
|
@ -5,6 +5,7 @@ from datetime import datetime, date, timedelta
|
||||||
from .models import *
|
from .models import *
|
||||||
from uncloud_service.models import GenericServiceProduct
|
from uncloud_service.models import GenericServiceProduct
|
||||||
|
|
||||||
|
|
||||||
class ProductOrderTestCase(TestCase):
|
class ProductOrderTestCase(TestCase):
|
||||||
"""
|
"""
|
||||||
Test products and products <-> order interaction
|
Test products and products <-> order interaction
|
||||||
|
@ -15,12 +16,53 @@ class ProductOrderTestCase(TestCase):
|
||||||
username='random_user',
|
username='random_user',
|
||||||
email='jane.random@domain.tld')
|
email='jane.random@domain.tld')
|
||||||
|
|
||||||
def test_update_one_time_product(self):
|
ba = BillingAddress.objects.create(
|
||||||
|
owner=self.user,
|
||||||
|
organization = 'Test org',
|
||||||
|
street="unknown",
|
||||||
|
city="unknown",
|
||||||
|
postal_code="somewhere else",
|
||||||
|
active=True)
|
||||||
|
|
||||||
|
def test_create_one_time_product(self):
|
||||||
"""
|
"""
|
||||||
One time payment products cannot be updated - can they?
|
One time payment products cannot be updated - can they?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
p = SampleOneTimeProduct.objects.create(owner=self.user)
|
||||||
|
|
||||||
|
self.assertEqual(p.one_time_price, 5)
|
||||||
|
self.assertEqual(p.recurring_price, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_order_creates_correct_order_count(self):
|
||||||
|
"""
|
||||||
|
Ensure creating orders from product only creates 1 order
|
||||||
|
"""
|
||||||
|
|
||||||
|
# One order
|
||||||
|
p = SampleOneTimeProduct(owner=self.user)
|
||||||
|
p.create_order_at(timezone.make_aware(datetime.datetime(2020,3,3)))
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
order_count = Order.objects.filter(owner=self.user).count()
|
||||||
|
self.assertEqual(order_count, 1)
|
||||||
|
|
||||||
|
# One more order
|
||||||
|
p = SampleRecurringProduct(owner=self.user)
|
||||||
|
p.create_order_at(timezone.make_aware(datetime.datetime(2020,3,3)))
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
order_count = Order.objects.filter(owner=self.user).count()
|
||||||
|
self.assertEqual(order_count, 2)
|
||||||
|
|
||||||
|
# Should create 2 orders
|
||||||
|
p = SampleRecurringProductOneTimeFee(owner=self.user)
|
||||||
|
p.create_order_at(timezone.make_aware(datetime.datetime(2020,3,3)))
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
order_count = Order.objects.filter(owner=self.user).count()
|
||||||
|
self.assertEqual(order_count, 4)
|
||||||
|
|
||||||
|
|
||||||
class BillingAddressTestCase(TestCase):
|
class BillingAddressTestCase(TestCase):
|
||||||
|
|
|
@ -69,16 +69,6 @@ class VMProduct(Product):
|
||||||
def recurring_price(self):
|
def recurring_price(self):
|
||||||
return self.cores * 3 + self.ram_in_gb * 4
|
return self.cores * 3 + self.ram_in_gb * 4
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if self.name:
|
|
||||||
name = f"{self.name} ({self.id})"
|
|
||||||
else:
|
|
||||||
name = self.id
|
|
||||||
|
|
||||||
return "VM {}: {} cores {} gb ram".format(name,
|
|
||||||
self.cores,
|
|
||||||
self.ram_in_gb)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
return "Virtual machine '{}': {} core(s), {}GB memory".format(
|
return "Virtual machine '{}': {} core(s), {}GB memory".format(
|
||||||
|
@ -92,6 +82,17 @@ class VMProduct(Product):
|
||||||
RecurringPeriod.choices))
|
RecurringPeriod.choices))
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
class VMWithOSProduct(VMProduct):
|
class VMWithOSProduct(VMProduct):
|
||||||
primary_disk = models.ForeignKey('VMDiskProduct', on_delete=models.CASCADE, null=True)
|
primary_disk = models.ForeignKey('VMDiskProduct', on_delete=models.CASCADE, null=True)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue