Move django-based uncloud to top-level
This commit is contained in:
		
					parent
					
						
							
								0560063326
							
						
					
				
			
			
				commit
				
					
						95d43f002f
					
				
			
		
					 265 changed files with 0 additions and 0 deletions
				
			
		
							
								
								
									
										0
									
								
								uncloud_service/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								uncloud_service/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										3
									
								
								uncloud_service/admin.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								uncloud_service/admin.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
from django.contrib import admin
 | 
			
		||||
 | 
			
		||||
# Register your models here.
 | 
			
		||||
							
								
								
									
										5
									
								
								uncloud_service/apps.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								uncloud_service/apps.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
from django.apps import AppConfig
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UngleichServiceConfig(AppConfig):
 | 
			
		||||
    name = 'ungleich_service'
 | 
			
		||||
							
								
								
									
										36
									
								
								uncloud_service/migrations/0001_initial.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								uncloud_service/migrations/0001_initial.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
# Generated by Django 3.0.5 on 2020-04-13 09:38
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
import django.contrib.postgres.fields.jsonb
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    initial = True
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('uncloud_pay', '0005_auto_20200413_0924'),
 | 
			
		||||
        ('uncloud_vm', '0010_auto_20200413_0924'),
 | 
			
		||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='MatrixServiceProduct',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('extra_data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, editable=False, null=True)),
 | 
			
		||||
                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
 | 
			
		||||
                ('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='PENDING', max_length=32)),
 | 
			
		||||
                ('domain', models.CharField(default='domain.tld', max_length=255)),
 | 
			
		||||
                ('order', models.ForeignKey(editable=False, 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)),
 | 
			
		||||
                ('vm', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='uncloud_vm.VMProduct')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'abstract': False,
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
							
								
								
									
										41
									
								
								uncloud_service/migrations/0002_auto_20200418_0641.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								uncloud_service/migrations/0002_auto_20200418_0641.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
# Generated by Django 3.0.5 on 2020-04-18 06:41
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
import django.contrib.postgres.fields.jsonb
 | 
			
		||||
import django.core.validators
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
			
		||||
        ('uncloud_pay', '0005_auto_20200413_0924'),
 | 
			
		||||
        ('uncloud_service', '0001_initial'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='matrixserviceproduct',
 | 
			
		||||
            name='status',
 | 
			
		||||
            field=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),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='GenericServiceProduct',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('extra_data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, editable=False, null=True)),
 | 
			
		||||
                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
 | 
			
		||||
                ('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)),
 | 
			
		||||
                ('custom_description', models.TextField()),
 | 
			
		||||
                ('custom_recurring_price', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, validators=[django.core.validators.MinValueValidator(0)])),
 | 
			
		||||
                ('custom_one_time_price', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, validators=[django.core.validators.MinValueValidator(0)])),
 | 
			
		||||
                ('order', models.ForeignKey(editable=False, 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,
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
							
								
								
									
										0
									
								
								uncloud_service/migrations/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								uncloud_service/migrations/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										64
									
								
								uncloud_service/models.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								uncloud_service/models.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
import uuid
 | 
			
		||||
 | 
			
		||||
from django.db import models
 | 
			
		||||
from uncloud_pay.models import Product, RecurringPeriod, AMOUNT_MAX_DIGITS, AMOUNT_DECIMALS
 | 
			
		||||
from uncloud_vm.models import VMProduct, VMDiskImageProduct
 | 
			
		||||
from django.core.validators import MinValueValidator
 | 
			
		||||
 | 
			
		||||
class MatrixServiceProduct(Product):
 | 
			
		||||
    monthly_managment_fee = 20
 | 
			
		||||
 | 
			
		||||
    description = "Managed Matrix HomeServer"
 | 
			
		||||
 | 
			
		||||
    # Specific to Matrix-as-a-Service
 | 
			
		||||
    vm = models.ForeignKey(
 | 
			
		||||
            VMProduct, on_delete=models.CASCADE
 | 
			
		||||
            )
 | 
			
		||||
    domain = models.CharField(max_length=255, default='domain.tld')
 | 
			
		||||
 | 
			
		||||
    # Default recurring price is PER_MONT, see Product class.
 | 
			
		||||
    def recurring_price(self, recurring_period=RecurringPeriod.PER_MONTH):
 | 
			
		||||
        return self.monthly_managment_fee
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def base_image():
 | 
			
		||||
        # TODO: find a way to safely reference debian 10 image.
 | 
			
		||||
        return VMDiskImageProduct.objects.get(uuid="93e564c5-adb3-4741-941f-718f76075f02")
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def allowed_recurring_periods():
 | 
			
		||||
        return list(filter(
 | 
			
		||||
            lambda pair: pair[0] in [RecurringPeriod.PER_MONTH],
 | 
			
		||||
            RecurringPeriod.choices))
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def one_time_price(self):
 | 
			
		||||
        return 30
 | 
			
		||||
 | 
			
		||||
class GenericServiceProduct(Product):
 | 
			
		||||
    custom_description = models.TextField()
 | 
			
		||||
    custom_recurring_price = models.DecimalField(default=0.0,
 | 
			
		||||
            max_digits=AMOUNT_MAX_DIGITS,
 | 
			
		||||
            decimal_places=AMOUNT_DECIMALS,
 | 
			
		||||
            validators=[MinValueValidator(0)])
 | 
			
		||||
    custom_one_time_price = models.DecimalField(default=0.0,
 | 
			
		||||
            max_digits=AMOUNT_MAX_DIGITS,
 | 
			
		||||
            decimal_places=AMOUNT_DECIMALS,
 | 
			
		||||
            validators=[MinValueValidator(0)])
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def recurring_price(self):
 | 
			
		||||
        # FIXME: handle recurring_period somehow.
 | 
			
		||||
        return self.custom_recurring_price
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def description(self):
 | 
			
		||||
        return self.custom_description
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def one_time_price(self):
 | 
			
		||||
        return self.custom_one_time_price
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def allowed_recurring_periods():
 | 
			
		||||
        return RecurringPeriod.choices
 | 
			
		||||
							
								
								
									
										60
									
								
								uncloud_service/serializers.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								uncloud_service/serializers.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,60 @@
 | 
			
		|||
from rest_framework import serializers
 | 
			
		||||
from .models import *
 | 
			
		||||
from uncloud_vm.serializers import ManagedVMProductSerializer
 | 
			
		||||
from uncloud_vm.models import VMProduct
 | 
			
		||||
from uncloud_pay.models import RecurringPeriod, BillingAddress
 | 
			
		||||
 | 
			
		||||
# XXX: the OrderSomethingSomthingProductSerializer classes add a lot of
 | 
			
		||||
# boilerplate: can we reduce it somehow?
 | 
			
		||||
 | 
			
		||||
class MatrixServiceProductSerializer(serializers.ModelSerializer):
 | 
			
		||||
    vm = ManagedVMProductSerializer()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = MatrixServiceProduct
 | 
			
		||||
        fields = ['uuid', 'order', 'owner', 'status', 'vm', 'domain',
 | 
			
		||||
                'recurring_period']
 | 
			
		||||
        read_only_fields = ['uuid', 'order', 'owner', 'status']
 | 
			
		||||
 | 
			
		||||
class OrderMatrixServiceProductSerializer(MatrixServiceProductSerializer):
 | 
			
		||||
    recurring_period = serializers.ChoiceField(
 | 
			
		||||
            choices=MatrixServiceProduct.allowed_recurring_periods())
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super(OrderMatrixServiceProductSerializer, self).__init__(*args, **kwargs)
 | 
			
		||||
        self.fields['billing_address'] = serializers.ChoiceField(
 | 
			
		||||
                choices=BillingAddress.get_addresses_for(
 | 
			
		||||
                    self.context['request'].user)
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = MatrixServiceProductSerializer.Meta.model
 | 
			
		||||
        fields = MatrixServiceProductSerializer.Meta.fields + [
 | 
			
		||||
                'recurring_period', 'billing_address'
 | 
			
		||||
                ]
 | 
			
		||||
        read_only_fields = MatrixServiceProductSerializer.Meta.read_only_fields
 | 
			
		||||
 | 
			
		||||
class GenericServiceProductSerializer(serializers.ModelSerializer):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = GenericServiceProduct
 | 
			
		||||
        fields = ['uuid', 'order', 'owner', 'status', 'custom_recurring_price',
 | 
			
		||||
                'custom_description', 'custom_one_time_price']
 | 
			
		||||
        read_only_fields = ['uuid', 'order', 'owner', 'status']
 | 
			
		||||
 | 
			
		||||
class OrderGenericServiceProductSerializer(GenericServiceProductSerializer):
 | 
			
		||||
    recurring_period = serializers.ChoiceField(
 | 
			
		||||
            choices=GenericServiceProduct.allowed_recurring_periods())
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super(OrderGenericServiceProductSerializer, self).__init__(*args, **kwargs)
 | 
			
		||||
        self.fields['billing_address'] = serializers.ChoiceField(
 | 
			
		||||
                choices=BillingAddress.get_addresses_for(
 | 
			
		||||
                    self.context['request'].user)
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = GenericServiceProductSerializer.Meta.model
 | 
			
		||||
        fields = GenericServiceProductSerializer.Meta.fields + [
 | 
			
		||||
                'recurring_period', 'billing_address'
 | 
			
		||||
                ]
 | 
			
		||||
        read_only_fields = GenericServiceProductSerializer.Meta.read_only_fields
 | 
			
		||||
							
								
								
									
										3
									
								
								uncloud_service/tests.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								uncloud_service/tests.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
from django.test import TestCase
 | 
			
		||||
 | 
			
		||||
# Create your tests here.
 | 
			
		||||
							
								
								
									
										128
									
								
								uncloud_service/views.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								uncloud_service/views.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,128 @@
 | 
			
		|||
from rest_framework import viewsets, permissions
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
from .models import *
 | 
			
		||||
from .serializers import *
 | 
			
		||||
 | 
			
		||||
from uncloud_pay.helpers import ProductViewSet
 | 
			
		||||
from uncloud_pay.models import Order
 | 
			
		||||
from uncloud_vm.models import VMProduct, VMDiskProduct
 | 
			
		||||
 | 
			
		||||
def create_managed_vm(cores, ram, disk_size, image, order):
 | 
			
		||||
    # Create VM
 | 
			
		||||
    disk = VMDiskProduct(
 | 
			
		||||
            owner=order.owner,
 | 
			
		||||
            order=order,
 | 
			
		||||
            size_in_gb=disk_size,
 | 
			
		||||
            image=image)
 | 
			
		||||
    vm = VMProduct(
 | 
			
		||||
            name="Managed Service Host",
 | 
			
		||||
            owner=order.owner,
 | 
			
		||||
            cores=cores,
 | 
			
		||||
            ram_in_gb=ram,
 | 
			
		||||
            primary_disk=disk)
 | 
			
		||||
    disk.vm = vm
 | 
			
		||||
 | 
			
		||||
    vm.save()
 | 
			
		||||
    disk.save()
 | 
			
		||||
 | 
			
		||||
    return vm
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MatrixServiceProductViewSet(ProductViewSet):
 | 
			
		||||
    permission_classes = [permissions.IsAuthenticated]
 | 
			
		||||
    serializer_class = MatrixServiceProductSerializer
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return MatrixServiceProduct.objects.filter(owner=self.request.user)
 | 
			
		||||
 | 
			
		||||
    def get_serializer_class(self):
 | 
			
		||||
        if self.action == 'create':
 | 
			
		||||
            return OrderMatrixServiceProductSerializer
 | 
			
		||||
        else:
 | 
			
		||||
            return MatrixServiceProductSerializer
 | 
			
		||||
 | 
			
		||||
    @transaction.atomic
 | 
			
		||||
    def create(self, request):
 | 
			
		||||
        # Extract serializer data.
 | 
			
		||||
        serializer = self.get_serializer(data=request.data)
 | 
			
		||||
        serializer.is_valid(raise_exception=True)
 | 
			
		||||
        order_recurring_period = serializer.validated_data.pop("recurring_period")
 | 
			
		||||
        order_billing_address = serializer.validated_data.pop("billing_address")
 | 
			
		||||
 | 
			
		||||
        # Create base order.)
 | 
			
		||||
        order = Order.objects.create(
 | 
			
		||||
                recurring_period=order_recurring_period,
 | 
			
		||||
                owner=request.user,
 | 
			
		||||
                billing_address=order_billing_address,
 | 
			
		||||
                starting_date=timezone.now()
 | 
			
		||||
                )
 | 
			
		||||
        order.save()
 | 
			
		||||
 | 
			
		||||
        # Create unerderlying VM.
 | 
			
		||||
        data = serializer.validated_data.pop('vm')
 | 
			
		||||
        vm = create_managed_vm(
 | 
			
		||||
                order=order,
 | 
			
		||||
                cores=data['cores'],
 | 
			
		||||
                ram=data['ram_in_gb'],
 | 
			
		||||
                disk_size=data['primary_disk']['size_in_gb'],
 | 
			
		||||
                image=MatrixServiceProduct.base_image())
 | 
			
		||||
 | 
			
		||||
        # Create service.
 | 
			
		||||
        service = serializer.save(
 | 
			
		||||
                order=order,
 | 
			
		||||
                owner=request.user,
 | 
			
		||||
                vm=vm)
 | 
			
		||||
 | 
			
		||||
        return Response(serializer.data)
 | 
			
		||||
 | 
			
		||||
class GenericServiceProductViewSet(ProductViewSet):
 | 
			
		||||
    permission_classes = [permissions.IsAuthenticated]
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        return GenericServiceProduct.objects.filter(owner=self.request.user)
 | 
			
		||||
 | 
			
		||||
    def get_serializer_class(self):
 | 
			
		||||
        if self.action == 'create':
 | 
			
		||||
            return OrderGenericServiceProductSerializer
 | 
			
		||||
        else:
 | 
			
		||||
            return GenericServiceProductSerializer
 | 
			
		||||
 | 
			
		||||
    @transaction.atomic
 | 
			
		||||
    def create(self, request):
 | 
			
		||||
        # Extract serializer data.
 | 
			
		||||
        serializer = self.get_serializer(data=request.data)
 | 
			
		||||
        serializer.is_valid(raise_exception=True)
 | 
			
		||||
        order_recurring_period = serializer.validated_data.pop("recurring_period")
 | 
			
		||||
        order_billing_address = serializer.validated_data.pop("billing_address")
 | 
			
		||||
 | 
			
		||||
        # Create base order.
 | 
			
		||||
        order = Order.objects.create(
 | 
			
		||||
                recurring_period=order_recurring_period,
 | 
			
		||||
                owner=request.user,
 | 
			
		||||
                billing_address=order_billing_address,
 | 
			
		||||
                starting_date=timezone.now()
 | 
			
		||||
                )
 | 
			
		||||
        order.save()
 | 
			
		||||
 | 
			
		||||
        # Create service.
 | 
			
		||||
        print(serializer.validated_data)
 | 
			
		||||
        service = serializer.save(order=order, owner=request.user)
 | 
			
		||||
 | 
			
		||||
        # XXX: Move this to some kind of on_create hook in parent
 | 
			
		||||
        # Product class?
 | 
			
		||||
        order.add_record(
 | 
			
		||||
                service.one_time_price,
 | 
			
		||||
                service.recurring_price,
 | 
			
		||||
                service.description)
 | 
			
		||||
 | 
			
		||||
        # XXX: Move this to some kind of on_create hook in parent
 | 
			
		||||
        # Product class?
 | 
			
		||||
        order.add_record(
 | 
			
		||||
                service.one_time_price,
 | 
			
		||||
                service.recurring_price,
 | 
			
		||||
                service.description)
 | 
			
		||||
 | 
			
		||||
        return Response(serializer.data)
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue