From 1ca247148c79620213b31260dabfbef90fb338d3 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Thu, 27 Feb 2020 11:21:38 +0100
Subject: [PATCH 01/23] [uncloud_pay] add "prototype"

---
 uncloud/uncloud_pay/__init__.py            |  0
 uncloud/uncloud_pay/admin.py               |  3 +
 uncloud/uncloud_pay/apps.py                |  5 ++
 uncloud/uncloud_pay/migrations/__init__.py |  0
 uncloud/uncloud_pay/models.py              | 91 ++++++++++++++++++++++
 uncloud/uncloud_pay/tests.py               |  3 +
 uncloud/uncloud_pay/views.py               | 55 +++++++++++++
 7 files changed, 157 insertions(+)
 create mode 100644 uncloud/uncloud_pay/__init__.py
 create mode 100644 uncloud/uncloud_pay/admin.py
 create mode 100644 uncloud/uncloud_pay/apps.py
 create mode 100644 uncloud/uncloud_pay/migrations/__init__.py
 create mode 100644 uncloud/uncloud_pay/models.py
 create mode 100644 uncloud/uncloud_pay/tests.py
 create mode 100644 uncloud/uncloud_pay/views.py

diff --git a/uncloud/uncloud_pay/__init__.py b/uncloud/uncloud_pay/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/uncloud/uncloud_pay/admin.py b/uncloud/uncloud_pay/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/uncloud/uncloud_pay/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/uncloud/uncloud_pay/apps.py b/uncloud/uncloud_pay/apps.py
new file mode 100644
index 0000000..051ffb4
--- /dev/null
+++ b/uncloud/uncloud_pay/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class UncloudPayConfig(AppConfig):
+    name = 'uncloud_pay'
diff --git a/uncloud/uncloud_pay/migrations/__init__.py b/uncloud/uncloud_pay/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py
new file mode 100644
index 0000000..6910d58
--- /dev/null
+++ b/uncloud/uncloud_pay/models.py
@@ -0,0 +1,91 @@
+from django.db import models
+from django.contrib.auth import get_user_model
+
+# Create your models here.
+
+
+class Bill(models.Model):
+    owner = models.ForeignKey(get_user_model(),
+            on_delete=models.CASCADE,
+            editable=False)
+
+    creation_date = models.DateTimeField()
+    starting_date = models.DateTimeField()
+    ending_date = models.DateTimeField()
+    due_date = models.DateField()
+
+    paid = models.BooleanField(default=False)
+    valid = models.BooleanField(default=True)
+
+    @property
+    def amount(self):
+        # iterate over all related orders
+        pass
+
+
+class Order(models.Model):
+    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
+    owner = models.ForeignKey(get_user_model(),
+                              on_delete=models.CASCADE,
+                              editable=False)
+
+    creation_date = models.DateTimeField()
+    starting_date = models.DateTimeField()
+    ending_date = models.DateTimeField(blank=True,
+                                       null=True)
+
+    bill = models.ManyToManyField(Bill,
+                                  on_delete=models.CASCADE,
+                                  editable=False,
+                                  blank=True,
+                                  null=True)
+
+
+    recurring_price = models.FloatField(editable=False)
+    one_time_price = models.FloatField(editable=False)
+
+    recurring_period = models.CharField(max_length=32,
+                              choices = (
+                                  ('onetime', 'Onetime'),
+                                  ('per_year', 'Per Year'),
+                                  ('per_month', 'Per Month'),
+                                  ('per_week', 'Per Week'),
+                                  ('per_day', 'Per Day'),
+                                  ('per_hour', 'Per Hour'),
+                                  ('per_minute', 'Per Minute'),
+                                  ('per_second', 'Per Second'),
+                              ),
+                              default='onetime'
+
+    )
+
+    # def amount(self):
+    #     amount = recurring_price
+    #     if recurring and first_month:
+    #         amount += one_time_price
+
+    #     return amount # you get the picture
+
+
+
+class Payment(models.Model):
+    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
+
+    owner = models.ForeignKey(get_user_model(),
+            on_delete=models.CASCADE,
+            editable=False)
+
+    amount = models.DecimalField(
+            default=0.0,
+            validators=[MinValueValidator(0)])
+
+    source = models.CharField(max_length=256,
+                              choices = (
+                                  ('wire', 'Wire Transfer'),
+                                  ('strip', 'Stripe'),
+                                  ('voucher', 'Voucher'),
+                                  ('referral', 'Referral'),
+                                  ('unknown', 'Unknown')
+                              ),
+                              default='unknown')
+    timestamp = models.DateTimeField(editable=False)
diff --git a/uncloud/uncloud_pay/tests.py b/uncloud/uncloud_pay/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/uncloud/uncloud_pay/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py
new file mode 100644
index 0000000..b52a2b6
--- /dev/null
+++ b/uncloud/uncloud_pay/views.py
@@ -0,0 +1,55 @@
+from django.shortcuts import render
+
+# Create your views here.
+
+
+# to be implemented
+class BalanceViewSet(viewsets.ModelViewSet):
+    # here we return a number
+    # number = sum(payments) - sum(bills)
+
+    bills = Bills.objects.filter(owner=self.request.user)
+    payments = Payment.objects.filter(owner=self.request.user)
+
+    # sum_paid = sum([ amount for amount payments..,.  ]) # you get the picture
+    # sum_to_be_paid = sum([ amount for amount bills..,.  ]) # you get the picture
+
+
+class Bills(viewset.ModelViewSet):
+    def unpaid(self, request):
+        return Bills.objects.filter(owner=self.request.user, paid=False)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    serializer_class = BillSerializer
+    permission_classes = [permissions.IsAuthenticated]
+    http_method_names = ['get']
+
+    def get_queryset(self):
+        return self.request.user.get_bills()

From a58a3612544565178a77a81beec4e6dbfec591d3 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Thu, 27 Feb 2020 11:36:50 +0100
Subject: [PATCH 02/23] Move snapshot to _pay and _vm

---
 uncloud/README.md                  |   6 ++
 uncloud/uncloud_api/models.py      | 139 -----------------------------
 uncloud/uncloud_api/serializers.py |  12 ---
 uncloud/uncloud_api/views.py       |  42 +--------
 uncloud/uncloud_pay/models.py      |  28 ++++++
 uncloud/uncloud_vm/models.py       |  45 ++++++++++
 uncloud/uncloud_vm/serializers.py  |  10 +++
 uncloud/uncloud_vm/views.py        |  50 +++++++++++
 8 files changed, 140 insertions(+), 192 deletions(-)
 delete mode 100644 uncloud/uncloud_api/models.py

diff --git a/uncloud/README.md b/uncloud/README.md
index 67f960f..19896d9 100644
--- a/uncloud/README.md
+++ b/uncloud/README.md
@@ -51,6 +51,12 @@ Installing the postgresql service is os dependent, but some hints:
 * Alpine: `apk add postgresql-server && rc-update add postgresql && rc-service  postgresql start`
 * Debian/Devuan: `apt install postgresql`
 
+After postresql is started, apply the migrations:
+
+```
+python manage.py migrate
+```
+
 ### Secrets
 
 cp `uncloud/secrets_sample.py` to `uncloud/secrets.py` and replace the
diff --git a/uncloud/uncloud_api/models.py b/uncloud/uncloud_api/models.py
deleted file mode 100644
index 6a6f9c8..0000000
--- a/uncloud/uncloud_api/models.py
+++ /dev/null
@@ -1,139 +0,0 @@
-import uuid
-
-from django.db import models
-from django.contrib.auth import get_user_model
-
-# Product in DB vs. product in code
-# DB:
-# - need to define params (+param types) in db -> messy?
-# - get /products/ is easy / automatic
-#
-# code
-# - can have serializer/verification of fields easily in DRF
-# - can have per product side effects / extra code running
-# - might (??) make features easier??
-# - how to setup / query the recurring period (?)
-# - could get products list via getattr() + re ...Product() classes
-# -> this could include the url for ordering => /order/vm_snapshot (params)
-# ---> this would work with urlpatterns
-
-# Combination: create specific product in DB (?)
-# - a table per product (?) with 1 entry?
-
-# Orders
-# define state in DB
-# select a price from a product => product might change, order stays
-# params:
-# - the product uuid or name (?) => productuuid
-# - the product parameters => for each feature
-#
-
-# logs
-# Should have a log = ... => 1:n field for most models!
-
-class Product(models.Model):
-    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
-    owner = models.ForeignKey(get_user_model(),
-                              on_delete=models.CASCADE,
-                              editable=False)
-
-    # override these fields by default
-
-    description = ""
-    recurring_period = "not_recurring"
-
-    status = models.CharField(max_length=256,
-                              choices = (
-                                  ('pending', 'Pending'),
-                                  ('being_created', 'Being created'),
-                                  ('active', 'Active'),
-                                  ('deleted', 'Deleted')
-                              ),
-                              default='pending'
-    )
-
-    # This is calculated by each product and saved in the DB
-    recurring_price = models.FloatField(editable=False)
-    one_time_price = models.FloatField(editable=False)
-
-    # FIXME: need recurring_time_frame
-
-    class Meta:
-        abstract = True
-
-    def __str__(self):
-        return "{}".format(self.name)
-
-
-class VMSnapshotProduct(Product):
-    price_per_gb_ssd = 0.35
-    price_per_gb_hdd = 1.5/100
-
-    # This we need to get from the VM
-    gb_ssd = models.FloatField(editable=False)
-    gb_hdd = models.FloatField(editable=False)
-
-    vm_uuid = models.UUIDField()
-
-    # Need to setup recurring_price and one_time_price and recurring period
-
-    sample_ssd = 10
-    sample_hdd = 100
-
-    def recurring_price(self):
-        return 0
-
-    def one_time_price(self):
-        return 0
-
-    @classmethod
-    def sample_price(cls):
-        return cls.sample_ssd * cls.price_per_gb_ssd + cls.sample_hdd * cls.price_per_gb_hdd
-
-    description = "Create snapshot of a VM"
-    recurring_period = "monthly"
-
-    @classmethod
-    def pricing_model(cls):
-        return """
-Pricing is on monthly basis and storage prices are equivalent to the storage
-price in the VM.
-
-Price per GB SSD is: {}
-Price per GB HDD is: {}
-
-
-Sample price for a VM with {} GB SSD and {} GB HDD VM is: {}.
-""".format(cls.price_per_gb_ssd, cls.price_per_gb_hdd,
-           cls.sample_ssd, cls.sample_hdd, cls.sample_price())
-
-
-
-
-
-
-
-
-class Feature(models.Model):
-    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
-    name = models.CharField(max_length=256)
-
-    recurring_price = models.FloatField(default=0)
-    one_time_price = models.FloatField()
-
-    product = models.ForeignKey(Product, on_delete=models.CASCADE)
-
-    # params for "cpu": cpu_count -> int
-    # each feature can only have one parameters
-    # could call this "value" and set whether it is user usable
-    # has_value = True/False
-    # value = string  -> int (?)
-    # value_int
-    # value_str
-    # value_float
-
-    class Meta:
-        abstract = True
-
-    def __str__(self):
-        return "'{}' - '{}'".format(self.product, self.name)
diff --git a/uncloud/uncloud_api/serializers.py b/uncloud/uncloud_api/serializers.py
index 7dc3686..cd7fd14 100644
--- a/uncloud/uncloud_api/serializers.py
+++ b/uncloud/uncloud_api/serializers.py
@@ -3,8 +3,6 @@ from django.contrib.auth import get_user_model
 
 from rest_framework import serializers
 
-from .models import VMSnapshotProduct
-
 class UserSerializer(serializers.ModelSerializer):
     class Meta:
         model = get_user_model()
@@ -14,13 +12,3 @@ class GroupSerializer(serializers.HyperlinkedModelSerializer):
     class Meta:
         model = Group
         fields = ['url', 'name']
-
-class VMSnapshotSerializer(serializers.HyperlinkedModelSerializer):
-    class Meta:
-        model = VMSnapshotProduct
-        fields = ['uuid', 'status', 'recurring_price', 'one_time_price' ]
-
-class VMSnapshotCreateSerializer(serializers.HyperlinkedModelSerializer):
-    class Meta:
-        model = VMSnapshotProduct
-        fields = '__all__'
diff --git a/uncloud/uncloud_api/views.py b/uncloud/uncloud_api/views.py
index eb4cc77..7e5c6f9 100644
--- a/uncloud/uncloud_api/views.py
+++ b/uncloud/uncloud_api/views.py
@@ -17,46 +17,6 @@ import sys
 import re
 
 
-# POST /vm/snapshot/ vmuuid=... => create snapshot, returns snapshot uuid
-# GET /vm/snapshot => list
-# DEL /vm/snapshot/<uuid:uuid> => delete
-# create-list -> get, post => ListCreateAPIView
-# del on other!
-class VMSnapshotView(viewsets.ViewSet):
-    permission_classes = [permissions.IsAuthenticated]
-
-    def list(self, request):
-        queryset = VMSnapshotProduct.objects.filter(owner=request.user)
-        serializer = VMSnapshotSerializer(queryset, many=True, context={'request': request})
-        return Response(serializer.data)
-
-    def retrieve(self, request, pk=None):
-        queryset = VMSnapshotProduct.objects.filter(owner=request.user)
-        vm = get_object_or_404(queryset, pk=pk)
-        serializer = VMSnapshotSerializer(vm, context={'request': request})
-        return Response(serializer.data)
-
-    def create(self, request):
-        print(request.data)
-        serializer = VMSnapshotCreateSerializer(data=request.data)
-
-        serializer.gb_ssd = 12
-        serializer.gb_hdd = 120
-        print("F")
-        serializer.is_valid(raise_exception=True)
-
-        print(serializer)
-        print("A")
-        serializer.save()
-        print("B")
-
-
-        # snapshot = VMSnapshotProduct(owner=request.user,
-        #                              **serialzer.data)
-
-        return Response(serializer.data)
-
-
 
 # maybe drop or not --- we need something to guide the user!
 # class ProductsViewSet(viewsets.ViewSet):
@@ -91,4 +51,4 @@ class UserViewSet(viewsets.ModelViewSet):
     permission_classes = [permissions.IsAuthenticated]
 
     def get_queryset(self):
-        return self.request.user
+        return self.request.user
\ No newline at end of file
diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py
index 6910d58..831710b 100644
--- a/uncloud/uncloud_pay/models.py
+++ b/uncloud/uncloud_pay/models.py
@@ -89,3 +89,31 @@ class Payment(models.Model):
                               ),
                               default='unknown')
     timestamp = models.DateTimeField(editable=False)
+
+
+
+
+class Product(models.Model):
+    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
+    owner = models.ForeignKey(get_user_model(),
+                              on_delete=models.CASCADE,
+                              editable=False)
+
+    description = ""
+
+    status = models.CharField(max_length=256,
+                              choices = (
+                                  ('pending', 'Pending'),
+                                  ('being_created', 'Being created'),
+                                  ('active', 'Active'),
+                                  ('deleted', 'Deleted')
+                              ),
+                              default='pending'
+    )
+
+    order = models.ForeignKey(Order,
+                              on_delete=models.CASCADE,
+                              editable=False)
+
+    class Meta:
+        abstract = True
diff --git a/uncloud/uncloud_vm/models.py b/uncloud/uncloud_vm/models.py
index f4b68dd..12d188e 100644
--- a/uncloud/uncloud_vm/models.py
+++ b/uncloud/uncloud_vm/models.py
@@ -2,6 +2,8 @@ from django.db import models
 from django.contrib.auth import get_user_model
 import uuid
 
+from uncloud_pay.models import Product
+
 
 class VMHost(models.Model):
     uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
@@ -71,3 +73,46 @@ class OperatingSystemDisk(VMDiskProduct):
 class VMNetworkCard(models.Model):
     vm   = models.ForeignKey(VMProduct, on_delete=models.CASCADE)
     mac_address = models.IntegerField()
+
+
+class VMSnapshotProduct(Product):
+    price_per_gb_ssd = 0.35
+    price_per_gb_hdd = 1.5/100
+
+    # This we need to get from the VM
+    gb_ssd = models.FloatField(editable=False)
+    gb_hdd = models.FloatField(editable=False)
+
+    vm_uuid = models.UUIDField()
+
+    # Need to setup recurring_price and one_time_price and recurring period
+
+    sample_ssd = 10
+    sample_hdd = 100
+
+    def recurring_price(self):
+        return 0
+
+    def one_time_price(self):
+        return 0
+
+    @classmethod
+    def sample_price(cls):
+        return cls.sample_ssd * cls.price_per_gb_ssd + cls.sample_hdd * cls.price_per_gb_hdd
+
+    description = "Create snapshot of a VM"
+    recurring_period = "monthly"
+
+    @classmethod
+    def pricing_model(cls):
+        return """
+Pricing is on monthly basis and storage prices are equivalent to the storage
+price in the VM.
+
+Price per GB SSD is: {}
+Price per GB HDD is: {}
+
+
+Sample price for a VM with {} GB SSD and {} GB HDD VM is: {}.
+""".format(cls.price_per_gb_ssd, cls.price_per_gb_hdd,
+           cls.sample_ssd, cls.sample_hdd, cls.sample_price())
diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py
index 4154aee..d5549ad 100644
--- a/uncloud/uncloud_vm/serializers.py
+++ b/uncloud/uncloud_vm/serializers.py
@@ -13,3 +13,13 @@ class VMProductSerializer(serializers.HyperlinkedModelSerializer):
     class Meta:
         model = VMProduct
         fields = '__all__'
+
+class VMSnapshotProductSerializer(serializers.HyperlinkedModelSerializer):
+    class Meta:
+        model = VMSnapshotProduct
+        fields = ['uuid', 'status', 'recurring_price', 'one_time_price' ]
+
+class VMSnapshotProductCreateSerializer(serializers.HyperlinkedModelSerializer):
+    class Meta:
+        model = VMSnapshotProduct
+        fields = '__all__'
diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py
index 91e81e1..4f2f9f4 100644
--- a/uncloud/uncloud_vm/views.py
+++ b/uncloud/uncloud_vm/views.py
@@ -27,3 +27,53 @@ class VMProductViewSet(viewsets.ModelViewSet):
         serializer.save(owner=request.user)
 
         return Response(serializer.data)
+
+
+class VMSnapshotProductViewSet(viewsets.ModelViewSet):
+    permission_classes = [permissions.IsAuthenticated]
+    serializer_class = VMSnapshotProductSerializer
+
+    def get_queryset(self):
+        return VMSnapshotProduct.objects.filter(owner=self.request.user)
+
+    def create(self, request):
+        serializer = VMProductSerializer(data=request.data, context={'request': request})
+        serializer.is_valid(raise_exception=True)
+        serializer.save(owner=request.user)
+
+        return Response(serializer.data)
+
+
+class VMSnapshotProductView(viewsets.ViewSet):
+    permission_classes = [permissions.IsAuthenticated]
+
+    def list(self, request):
+        queryset = VMSnapshotProduct.objects.filter(owner=request.user)
+        serializer = VMSnapshotSerializer(queryset, many=True, context={'request': request})
+        return Response(serializer.data)
+
+    def retrieve(self, request, pk=None):
+        queryset = VMSnapshotProduct.objects.filter(owner=request.user)
+        vm = get_object_or_404(queryset, pk=pk)
+        serializer = VMSnapshotSerializer(vm, context={'request': request})
+        return Response(serializer.data)
+
+    def create(self, request):
+        print(request.data)
+        serializer = VMSnapshotCreateSerializer(data=request.data)
+
+        serializer.gb_ssd = 12
+        serializer.gb_hdd = 120
+        print("F")
+        serializer.is_valid(raise_exception=True)
+
+        print(serializer)
+        print("A")
+        serializer.save()
+        print("B")
+
+
+        # snapshot = VMSnapshotProduct(owner=request.user,
+        #                              **serialzer.data)
+
+        return Response(serializer.data)

From aa59b05a2dedfd65651d62bc00994d03cf34694b Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Thu, 27 Feb 2020 11:40:36 +0100
Subject: [PATCH 03/23] cleanup urls

---
 uncloud/uncloud/urls.py            | 7 +------
 uncloud/uncloud_api/serializers.py | 6 ------
 uncloud/uncloud_api/views.py       | 7 ++-----
 3 files changed, 3 insertions(+), 17 deletions(-)

diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py
index 23392c5..1e4c9d0 100644
--- a/uncloud/uncloud/urls.py
+++ b/uncloud/uncloud/urls.py
@@ -18,14 +18,12 @@ from django.urls import path, include
 
 from rest_framework import routers
 
-from uncloud_api import views as apiviews
 from uncloud_vm import views as vmviews
 from opennebula import views as oneviews
 
 router = routers.DefaultRouter()
 
-router.register(r'user', apiviews.UserViewSet, basename='user')
-
+# user / regular urls
 router.register(r'vm/snapshot', apiviews.VMSnapshotView, basename='VMSnapshot')
 router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct')
 
@@ -35,10 +33,7 @@ router.register(r'admin/opennebula', oneviews.VMViewSet, basename='opennebula')
 router.register(r'admin/opennebula_raw', oneviews.RawVMViewSet)
 
 
-# Wire up our API using automatic URL routing.
-# Additionally, we include login URLs for the browsable API.
 urlpatterns = [
     path('', include(router.urls)),
-    path('admin/', admin.site.urls), # login to django itself
     path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) # for login to REST API
 ]
diff --git a/uncloud/uncloud_api/serializers.py b/uncloud/uncloud_api/serializers.py
index cd7fd14..89f4e83 100644
--- a/uncloud/uncloud_api/serializers.py
+++ b/uncloud/uncloud_api/serializers.py
@@ -1,4 +1,3 @@
-from django.contrib.auth.models import Group
 from django.contrib.auth import get_user_model
 
 from rest_framework import serializers
@@ -7,8 +6,3 @@ class UserSerializer(serializers.ModelSerializer):
     class Meta:
         model = get_user_model()
         fields = ['url', 'username', 'email']
-
-class GroupSerializer(serializers.HyperlinkedModelSerializer):
-    class Meta:
-        model = Group
-        fields = ['url', 'name']
diff --git a/uncloud/uncloud_api/views.py b/uncloud/uncloud_api/views.py
index 7e5c6f9..c90b963 100644
--- a/uncloud/uncloud_api/views.py
+++ b/uncloud/uncloud_api/views.py
@@ -7,10 +7,7 @@ from rest_framework import viewsets, permissions, generics
 from rest_framework.views import APIView
 from rest_framework.response import Response
 
-from uncloud_vm.models import VMProduct
-from .models import VMSnapshotProduct
-from .serializers import UserSerializer, GroupSerializer, VMSnapshotSerializer, VMSnapshotCreateSerializer
-
+from .serializers import UserSerializer
 
 import inspect
 import sys
@@ -51,4 +48,4 @@ class UserViewSet(viewsets.ModelViewSet):
     permission_classes = [permissions.IsAuthenticated]
 
     def get_queryset(self):
-        return self.request.user
\ No newline at end of file
+        return self.request.user

From 11d629bb512854560cd5f720fd22921dca18c6ef Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Thu, 27 Feb 2020 11:42:42 +0100
Subject: [PATCH 04/23] [uncloud_api] completely remove it

---
 uncloud/uncloud_api/__init__.py               |  0
 uncloud/uncloud_api/admin.py                  |  6 ---
 uncloud/uncloud_api/apps.py                   |  5 --
 uncloud/uncloud_api/management/__init__.py    |  0
 .../management/commands/__init__.py           |  0
 .../uncloud_api/management/commands/hack.py   | 26 ----------
 .../management/commands/snapshot.py           | 29 -----------
 .../uncloud_api/migrations/0001_initial.py    | 31 -----------
 .../0002_vmsnapshotproduct_vm_uuid.py         | 19 -------
 .../migrations/0003_auto_20200225_1950.py     | 36 -------------
 uncloud/uncloud_api/migrations/__init__.py    |  0
 uncloud/uncloud_api/serializers.py            |  8 ---
 uncloud/uncloud_api/tests.py                  |  3 --
 uncloud/uncloud_api/views.py                  | 51 -------------------
 14 files changed, 214 deletions(-)
 delete mode 100644 uncloud/uncloud_api/__init__.py
 delete mode 100644 uncloud/uncloud_api/admin.py
 delete mode 100644 uncloud/uncloud_api/apps.py
 delete mode 100644 uncloud/uncloud_api/management/__init__.py
 delete mode 100644 uncloud/uncloud_api/management/commands/__init__.py
 delete mode 100644 uncloud/uncloud_api/management/commands/hack.py
 delete mode 100644 uncloud/uncloud_api/management/commands/snapshot.py
 delete mode 100644 uncloud/uncloud_api/migrations/0001_initial.py
 delete mode 100644 uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py
 delete mode 100644 uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py
 delete mode 100644 uncloud/uncloud_api/migrations/__init__.py
 delete mode 100644 uncloud/uncloud_api/serializers.py
 delete mode 100644 uncloud/uncloud_api/tests.py
 delete mode 100644 uncloud/uncloud_api/views.py

diff --git a/uncloud/uncloud_api/__init__.py b/uncloud/uncloud_api/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/uncloud/uncloud_api/admin.py b/uncloud/uncloud_api/admin.py
deleted file mode 100644
index d242668..0000000
--- a/uncloud/uncloud_api/admin.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.contrib import admin
-
-from .models import Product, Feature
-
-#admin.site.register(Product)
-#admin.site.register(Feature)
diff --git a/uncloud/uncloud_api/apps.py b/uncloud/uncloud_api/apps.py
deleted file mode 100644
index 6830fa2..0000000
--- a/uncloud/uncloud_api/apps.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.apps import AppConfig
-
-
-class ApiConfig(AppConfig):
-    name = 'uncloud_api'
diff --git a/uncloud/uncloud_api/management/__init__.py b/uncloud/uncloud_api/management/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/uncloud/uncloud_api/management/commands/__init__.py b/uncloud/uncloud_api/management/commands/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/uncloud/uncloud_api/management/commands/hack.py b/uncloud/uncloud_api/management/commands/hack.py
deleted file mode 100644
index e129952..0000000
--- a/uncloud/uncloud_api/management/commands/hack.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import time
-from django.conf import settings
-from django.core.management.base import BaseCommand
-
-import uncloud_api.models
-
-import inspect
-import sys
-import re
-
-class Command(BaseCommand):
-    args = '<None>'
-    help = 'hacking - only use if you are Nico'
-
-    def add_arguments(self, parser):
-        parser.add_argument('command', type=str, help='Command')
-
-    def handle(self, *args, **options):
-        getattr(self, options['command'])(**options)
-
-    @classmethod
-    def classtest(cls, **_):
-        clsmembers = inspect.getmembers(sys.modules['uncloud_api.models'], inspect.isclass)
-        for name, c in clsmembers:
-            if re.match(r'.+Product$', name):
-                print("{} -> {}".format(name, c))
diff --git a/uncloud/uncloud_api/management/commands/snapshot.py b/uncloud/uncloud_api/management/commands/snapshot.py
deleted file mode 100644
index 41d0e38..0000000
--- a/uncloud/uncloud_api/management/commands/snapshot.py
+++ /dev/null
@@ -1,29 +0,0 @@
-import time
-from django.conf import settings
-from django.core.management.base import BaseCommand
-
-from uncloud_api import models
-
-
-class Command(BaseCommand):
-    args = '<None>'
-    help = 'VM Snapshot support'
-
-    def add_arguments(self, parser):
-        parser.add_argument('command', type=str, help='Command')
-
-    def handle(self, *args, **options):
-        print("Snapshotting")
-        #getattr(self, options['command'])(**options)
-
-    @classmethod
-    def monitor(cls, **_):
-        while True:
-            try:
-                tweets = models.Reply.get_target_tweets()
-                responses = models.Reply.objects.values_list('tweet_id', flat=True)
-                new_tweets = [x for x in tweets if x.id not in responses]
-                models.Reply.send(new_tweets)
-            except TweepError as e:
-                print(e)
-            time.sleep(60)
diff --git a/uncloud/uncloud_api/migrations/0001_initial.py b/uncloud/uncloud_api/migrations/0001_initial.py
deleted file mode 100644
index 67bdd2e..0000000
--- a/uncloud/uncloud_api/migrations/0001_initial.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# Generated by Django 3.0.3 on 2020-02-23 17:12
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-import uuid
-
-
-class Migration(migrations.Migration):
-
-    initial = True
-
-    dependencies = [
-        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='VMSnapshotProduct',
-            fields=[
-                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
-                ('status', models.CharField(choices=[('pending', 'Pending'), ('being_created', 'Being created'), ('active', 'Active'), ('deleted', 'Deleted')], default='pending', max_length=256)),
-                ('gb_ssd', models.FloatField()),
-                ('gb_hdd', models.FloatField()),
-                ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
-            ],
-            options={
-                'abstract': False,
-            },
-        ),
-    ]
diff --git a/uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py b/uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py
deleted file mode 100644
index b35317e..0000000
--- a/uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Generated by Django 3.0.3 on 2020-02-25 18:16
-
-from django.db import migrations, models
-import uuid
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('uncloud_api', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='vmsnapshotproduct',
-            name='vm_uuid',
-            field=models.UUIDField(default=uuid.uuid4, editable=False),
-        ),
-    ]
diff --git a/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py b/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py
deleted file mode 100644
index be7624c..0000000
--- a/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Generated by Django 3.0.3 on 2020-02-25 19:50
-
-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_api', '0002_vmsnapshotproduct_vm_uuid'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='vmsnapshotproduct',
-            name='gb_hdd',
-            field=models.FloatField(editable=False),
-        ),
-        migrations.AlterField(
-            model_name='vmsnapshotproduct',
-            name='gb_ssd',
-            field=models.FloatField(editable=False),
-        ),
-        migrations.AlterField(
-            model_name='vmsnapshotproduct',
-            name='owner',
-            field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
-        ),
-        migrations.AlterField(
-            model_name='vmsnapshotproduct',
-            name='vm_uuid',
-            field=models.UUIDField(),
-        ),
-    ]
diff --git a/uncloud/uncloud_api/migrations/__init__.py b/uncloud/uncloud_api/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/uncloud/uncloud_api/serializers.py b/uncloud/uncloud_api/serializers.py
deleted file mode 100644
index 89f4e83..0000000
--- a/uncloud/uncloud_api/serializers.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django.contrib.auth import get_user_model
-
-from rest_framework import serializers
-
-class UserSerializer(serializers.ModelSerializer):
-    class Meta:
-        model = get_user_model()
-        fields = ['url', 'username', 'email']
diff --git a/uncloud/uncloud_api/tests.py b/uncloud/uncloud_api/tests.py
deleted file mode 100644
index 7ce503c..0000000
--- a/uncloud/uncloud_api/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/uncloud/uncloud_api/views.py b/uncloud/uncloud_api/views.py
deleted file mode 100644
index c90b963..0000000
--- a/uncloud/uncloud_api/views.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from django.shortcuts import render
-from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group
-
-from rest_framework import viewsets, permissions, generics
-
-from rest_framework.views import APIView
-from rest_framework.response import Response
-
-from .serializers import UserSerializer
-
-import inspect
-import sys
-import re
-
-
-
-# maybe drop or not --- we need something to guide the user!
-# class ProductsViewSet(viewsets.ViewSet):
-#     permission_classes = [permissions.IsAuthenticated]
-
-#     def list(self, request):
-
-#         clsmembers = []
-#         for modules in [ 'uncloud_api.models', 'uncloud_vm.models' ]:
-#             clsmembers.extend(inspect.getmembers(sys.modules[modules], inspect.isclass))
-
-
-#         products = []
-#         for name, c in clsmembers:
-#             # Include everything that ends in Product, but not Product itself
-#             m = re.match(r'(?P<pname>.+)Product$', name)
-#             if m:
-#                 products.append({
-#                     'name': m.group('pname'),
-#                     'description': c.description,
-#                     'recurring_period': c.recurring_period,
-#                     'pricing_model': c.pricing_model()
-#                 }
-#                 )
-
-
-#         return Response(products)
-
-
-class UserViewSet(viewsets.ModelViewSet):
-    serializer_class = UserSerializer
-    permission_classes = [permissions.IsAuthenticated]
-
-    def get_queryset(self):
-        return self.request.user

From 06ab21c577052f5a3fafee8b653bf9ea6d44a04d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= <timothee.floure@posteo.net>
Date: Thu, 27 Feb 2020 11:59:28 +0100
Subject: [PATCH 05/23] Fix python errors on latest hack commits => make
 runserver happy again

---
 uncloud/uncloud/settings.py                   |  1 +
 uncloud/uncloud/urls.py                       |  7 +-
 uncloud/uncloud_api/admin.py                  |  2 -
 .../0002_vmsnapshotproduct_vm_uuid.py         | 19 ------
 .../migrations/0003_auto_20200225_1950.py     | 36 ----------
 uncloud/uncloud_api/migrations/__init__.py    |  0
 .../uncloud_pay/migrations/0001_initial.py    | 56 ++++++++++++++++
 uncloud/uncloud_pay/models.py                 |  8 ++-
 uncloud/uncloud_pay/serializers.py            | 12 ++++
 uncloud/uncloud_pay/views.py                  | 65 ++++++++-----------
 .../migrations/0004_vmsnapshotproduct.py}     | 14 ++--
 uncloud/uncloud_vm/serializers.py             |  2 +-
 uncloud/uncloud_vm/views.py                   |  6 +-
 13 files changed, 121 insertions(+), 107 deletions(-)
 delete mode 100644 uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py
 delete mode 100644 uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py
 delete mode 100644 uncloud/uncloud_api/migrations/__init__.py
 create mode 100644 uncloud/uncloud_pay/migrations/0001_initial.py
 create mode 100644 uncloud/uncloud_pay/serializers.py
 rename uncloud/{uncloud_api/migrations/0001_initial.py => uncloud_vm/migrations/0004_vmsnapshotproduct.py} (57%)

diff --git a/uncloud/uncloud/settings.py b/uncloud/uncloud/settings.py
index 614cd25..05c4f35 100644
--- a/uncloud/uncloud/settings.py
+++ b/uncloud/uncloud/settings.py
@@ -61,6 +61,7 @@ INSTALLED_APPS = [
     'django.contrib.staticfiles',
     'rest_framework',
     'uncloud_api',
+    'uncloud_pay',
     'uncloud_auth',
     'uncloud_vm',
     'opennebula'
diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py
index 1e4c9d0..79958c5 100644
--- a/uncloud/uncloud/urls.py
+++ b/uncloud/uncloud/urls.py
@@ -19,14 +19,19 @@ from django.urls import path, include
 from rest_framework import routers
 
 from uncloud_vm import views as vmviews
+from uncloud_pay import views as payviews
 from opennebula import views as oneviews
 
 router = routers.DefaultRouter()
 
 # user / regular urls
-router.register(r'vm/snapshot', apiviews.VMSnapshotView, basename='VMSnapshot')
+router.register(r'vm/snapshot', vmviews.VMSnapshotProductView, basename='VMSnapshot')
 router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct')
 
+# Pay
+router.register(r'bill', payviews.BillViewSet, basename='bill')
+router.register(r'payment', payviews.PaymentViewSet, basename='payment')
+
 # admin/staff urls
 router.register(r'admin/vmhost', vmviews.VMHostViewSet)
 router.register(r'admin/opennebula', oneviews.VMViewSet, basename='opennebula')
diff --git a/uncloud/uncloud_api/admin.py b/uncloud/uncloud_api/admin.py
index d242668..03246ec 100644
--- a/uncloud/uncloud_api/admin.py
+++ b/uncloud/uncloud_api/admin.py
@@ -1,6 +1,4 @@
 from django.contrib import admin
 
-from .models import Product, Feature
-
 #admin.site.register(Product)
 #admin.site.register(Feature)
diff --git a/uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py b/uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py
deleted file mode 100644
index b35317e..0000000
--- a/uncloud/uncloud_api/migrations/0002_vmsnapshotproduct_vm_uuid.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Generated by Django 3.0.3 on 2020-02-25 18:16
-
-from django.db import migrations, models
-import uuid
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('uncloud_api', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='vmsnapshotproduct',
-            name='vm_uuid',
-            field=models.UUIDField(default=uuid.uuid4, editable=False),
-        ),
-    ]
diff --git a/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py b/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py
deleted file mode 100644
index be7624c..0000000
--- a/uncloud/uncloud_api/migrations/0003_auto_20200225_1950.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# Generated by Django 3.0.3 on 2020-02-25 19:50
-
-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_api', '0002_vmsnapshotproduct_vm_uuid'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='vmsnapshotproduct',
-            name='gb_hdd',
-            field=models.FloatField(editable=False),
-        ),
-        migrations.AlterField(
-            model_name='vmsnapshotproduct',
-            name='gb_ssd',
-            field=models.FloatField(editable=False),
-        ),
-        migrations.AlterField(
-            model_name='vmsnapshotproduct',
-            name='owner',
-            field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
-        ),
-        migrations.AlterField(
-            model_name='vmsnapshotproduct',
-            name='vm_uuid',
-            field=models.UUIDField(),
-        ),
-    ]
diff --git a/uncloud/uncloud_api/migrations/__init__.py b/uncloud/uncloud_api/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/uncloud/uncloud_pay/migrations/0001_initial.py b/uncloud/uncloud_pay/migrations/0001_initial.py
new file mode 100644
index 0000000..6e57c59
--- /dev/null
+++ b/uncloud/uncloud_pay/migrations/0001_initial.py
@@ -0,0 +1,56 @@
+# Generated by Django 3.0.3 on 2020-02-27 10:50
+
+from django.conf import settings
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Bill',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('creation_date', models.DateTimeField()),
+                ('starting_date', models.DateTimeField()),
+                ('ending_date', models.DateTimeField()),
+                ('due_date', models.DateField()),
+                ('paid', models.BooleanField(default=False)),
+                ('valid', models.BooleanField(default=True)),
+                ('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Payment',
+            fields=[
+                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+                ('amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=10, validators=[django.core.validators.MinValueValidator(0)])),
+                ('source', models.CharField(choices=[('wire', 'Wire Transfer'), ('strip', 'Stripe'), ('voucher', 'Voucher'), ('referral', 'Referral'), ('unknown', 'Unknown')], default='unknown', max_length=256)),
+                ('timestamp', models.DateTimeField(editable=False)),
+                ('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Order',
+            fields=[
+                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+                ('creation_date', models.DateTimeField()),
+                ('starting_date', models.DateTimeField()),
+                ('ending_date', models.DateTimeField(blank=True, null=True)),
+                ('recurring_price', models.FloatField(editable=False)),
+                ('one_time_price', models.FloatField(editable=False)),
+                ('recurring_period', models.CharField(choices=[('onetime', 'Onetime'), ('per_year', 'Per Year'), ('per_month', 'Per Month'), ('per_week', 'Per Week'), ('per_day', 'Per Day'), ('per_hour', 'Per Hour'), ('per_minute', 'Per Minute'), ('per_second', 'Per Second')], default='onetime', max_length=32)),
+                ('bill', models.ManyToManyField(blank=True, editable=False, null=True, to='uncloud_pay.Bill')),
+                ('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+    ]
diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py
index 831710b..71653fa 100644
--- a/uncloud/uncloud_pay/models.py
+++ b/uncloud/uncloud_pay/models.py
@@ -1,8 +1,11 @@
 from django.db import models
 from django.contrib.auth import get_user_model
+from django.core.validators import MinValueValidator
 
-# Create your models here.
+import uuid
 
+AMOUNT_MAX_DIGITS=10
+AMOUNT_DECIMALS=2
 
 class Bill(models.Model):
     owner = models.ForeignKey(get_user_model(),
@@ -35,7 +38,6 @@ class Order(models.Model):
                                        null=True)
 
     bill = models.ManyToManyField(Bill,
-                                  on_delete=models.CASCADE,
                                   editable=False,
                                   blank=True,
                                   null=True)
@@ -77,6 +79,8 @@ class Payment(models.Model):
 
     amount = models.DecimalField(
             default=0.0,
+            max_digits=AMOUNT_MAX_DIGITS,
+            decimal_places=AMOUNT_DECIMALS,
             validators=[MinValueValidator(0)])
 
     source = models.CharField(max_length=256,
diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py
new file mode 100644
index 0000000..e11544b
--- /dev/null
+++ b/uncloud/uncloud_pay/serializers.py
@@ -0,0 +1,12 @@
+from rest_framework import serializers
+from .models import Bill, Payment
+
+class BillSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Bill
+        fields = ['user', 'amount']
+
+class PaymentSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Payment
+        fields = ['user', 'amount', 'source', 'timestamp']
diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py
index b52a2b6..8fc02ea 100644
--- a/uncloud/uncloud_pay/views.py
+++ b/uncloud/uncloud_pay/views.py
@@ -1,55 +1,46 @@
 from django.shortcuts import render
+from rest_framework import viewsets, permissions
 
-# Create your views here.
+from .models import Bill, Payment
+from .serializers import BillSerializer, PaymentSerializer
 
 
 # to be implemented
-class BalanceViewSet(viewsets.ModelViewSet):
+class BalanceViewSet(viewsets.ViewSet):
     # here we return a number
     # number = sum(payments) - sum(bills)
 
-    bills = Bills.objects.filter(owner=self.request.user)
-    payments = Payment.objects.filter(owner=self.request.user)
+    #bills = Bill.objects.filter(owner=self.request.user)
+    #payments = Payment.objects.filter(owner=self.request.user)
 
     # sum_paid = sum([ amount for amount payments..,.  ]) # you get the picture
     # sum_to_be_paid = sum([ amount for amount bills..,.  ]) # you get the picture
+    pass
 
 
-class Bills(viewset.ModelViewSet):
-    def unpaid(self, request):
-        return Bills.objects.filter(owner=self.request.user, paid=False)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+class BillViewSet(viewsets.ModelViewSet):
     serializer_class = BillSerializer
     permission_classes = [permissions.IsAuthenticated]
     http_method_names = ['get']
 
     def get_queryset(self):
-        return self.request.user.get_bills()
+        return Bill.objects.filter(owner=self.request.user)
+
+    def unpaid(self, request):
+        return Bill.objects.filter(owner=self.request.user, paid=False)
+
+class PaymentViewSet(viewsets.ModelViewSet):
+    serializer_class = PaymentSerializer
+    permission_classes = [permissions.IsAuthenticated]
+    http_method_names = ['get', 'post']
+
+    def get_queryset(self):
+        return Payment.objects.filter(user=self.request.user)
+
+    def create(self, request):
+        serializer = self.get_serializer(data=request.data)
+        serializer.is_valid(raise_exception=True)
+        serializer.save(user=request.user,timestamp=datetime.now())
+
+        headers = self.get_success_headers(serializer.data)
+        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
diff --git a/uncloud/uncloud_api/migrations/0001_initial.py b/uncloud/uncloud_vm/migrations/0004_vmsnapshotproduct.py
similarity index 57%
rename from uncloud/uncloud_api/migrations/0001_initial.py
rename to uncloud/uncloud_vm/migrations/0004_vmsnapshotproduct.py
index 67bdd2e..13840b5 100644
--- a/uncloud/uncloud_api/migrations/0001_initial.py
+++ b/uncloud/uncloud_vm/migrations/0004_vmsnapshotproduct.py
@@ -1,4 +1,4 @@
-# Generated by Django 3.0.3 on 2020-02-23 17:12
+# Generated by Django 3.0.3 on 2020-02-27 10:50
 
 from django.conf import settings
 from django.db import migrations, models
@@ -8,10 +8,10 @@ import uuid
 
 class Migration(migrations.Migration):
 
-    initial = True
-
     dependencies = [
         migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('uncloud_pay', '0001_initial'),
+        ('uncloud_vm', '0003_auto_20200225_2028'),
     ]
 
     operations = [
@@ -20,9 +20,11 @@ class Migration(migrations.Migration):
             fields=[
                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
                 ('status', models.CharField(choices=[('pending', 'Pending'), ('being_created', 'Being created'), ('active', 'Active'), ('deleted', 'Deleted')], default='pending', max_length=256)),
-                ('gb_ssd', models.FloatField()),
-                ('gb_hdd', models.FloatField()),
-                ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+                ('gb_ssd', models.FloatField(editable=False)),
+                ('gb_hdd', models.FloatField(editable=False)),
+                ('vm_uuid', models.UUIDField()),
+                ('order', models.ForeignKey(editable=False, 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,
diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py
index d5549ad..232e954 100644
--- a/uncloud/uncloud_vm/serializers.py
+++ b/uncloud/uncloud_vm/serializers.py
@@ -1,7 +1,7 @@
 from django.contrib.auth import get_user_model
 
 from rest_framework import serializers
-from .models import VMHost, VMProduct
+from .models import VMHost, VMProduct, VMSnapshotProduct
 
 class VMHostSerializer(serializers.HyperlinkedModelSerializer):
     class Meta:
diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py
index 4f2f9f4..cb87e9d 100644
--- a/uncloud/uncloud_vm/views.py
+++ b/uncloud/uncloud_vm/views.py
@@ -7,7 +7,7 @@ from rest_framework import viewsets, permissions
 from rest_framework.response import Response
 
 from .models import VMHost, VMProduct
-from .serializers import VMHostSerializer, VMProductSerializer
+from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer
 
 class VMHostViewSet(viewsets.ModelViewSet):
     serializer_class = VMHostSerializer
@@ -49,13 +49,13 @@ class VMSnapshotProductView(viewsets.ViewSet):
 
     def list(self, request):
         queryset = VMSnapshotProduct.objects.filter(owner=request.user)
-        serializer = VMSnapshotSerializer(queryset, many=True, context={'request': request})
+        serializer = VMSnapshotProductSerializer(queryset, many=True, context={'request': request})
         return Response(serializer.data)
 
     def retrieve(self, request, pk=None):
         queryset = VMSnapshotProduct.objects.filter(owner=request.user)
         vm = get_object_or_404(queryset, pk=pk)
-        serializer = VMSnapshotSerializer(vm, context={'request': request})
+        serializer = VMSnapshotProductSerializer(vm, context={'request': request})
         return Response(serializer.data)
 
     def create(self, request):

From fd648ade6579334b953fe3944b25f1b489974d42 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Thu, 27 Feb 2020 12:02:41 +0100
Subject: [PATCH 06/23] ++cleanup

Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
---
 uncloud/uncloud/settings.py |  1 -
 uncloud/uncloud_vm/views.py | 45 ++++++++-----------------------------
 2 files changed, 9 insertions(+), 37 deletions(-)

diff --git a/uncloud/uncloud/settings.py b/uncloud/uncloud/settings.py
index 614cd25..899de1b 100644
--- a/uncloud/uncloud/settings.py
+++ b/uncloud/uncloud/settings.py
@@ -60,7 +60,6 @@ INSTALLED_APPS = [
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'rest_framework',
-    'uncloud_api',
     'uncloud_auth',
     'uncloud_vm',
     'opennebula'
diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py
index 4f2f9f4..aabf8c5 100644
--- a/uncloud/uncloud_vm/views.py
+++ b/uncloud/uncloud_vm/views.py
@@ -6,7 +6,9 @@ from django.shortcuts import get_object_or_404
 from rest_framework import viewsets, permissions
 from rest_framework.response import Response
 
-from .models import VMHost, VMProduct
+from .models import VMHost, VMProduct. VMSnapshotProduct
+from uncloud_pay.models import Order
+
 from .serializers import VMHostSerializer, VMProductSerializer
 
 class VMHostViewSet(viewsets.ModelViewSet):
@@ -14,6 +16,7 @@ class VMHostViewSet(viewsets.ModelViewSet):
     queryset = VMHost.objects.all()
     permission_classes = [permissions.IsAdminUser]
 
+
 class VMProductViewSet(viewsets.ModelViewSet):
     permission_classes = [permissions.IsAuthenticated]
     serializer_class = VMProductSerializer
@@ -37,43 +40,13 @@ class VMSnapshotProductViewSet(viewsets.ModelViewSet):
         return VMSnapshotProduct.objects.filter(owner=self.request.user)
 
     def create(self, request):
+
         serializer = VMProductSerializer(data=request.data, context={'request': request})
         serializer.is_valid(raise_exception=True)
+
+        # Create order
+        order = Order()
+
         serializer.save(owner=request.user)
 
         return Response(serializer.data)
-
-
-class VMSnapshotProductView(viewsets.ViewSet):
-    permission_classes = [permissions.IsAuthenticated]
-
-    def list(self, request):
-        queryset = VMSnapshotProduct.objects.filter(owner=request.user)
-        serializer = VMSnapshotSerializer(queryset, many=True, context={'request': request})
-        return Response(serializer.data)
-
-    def retrieve(self, request, pk=None):
-        queryset = VMSnapshotProduct.objects.filter(owner=request.user)
-        vm = get_object_or_404(queryset, pk=pk)
-        serializer = VMSnapshotSerializer(vm, context={'request': request})
-        return Response(serializer.data)
-
-    def create(self, request):
-        print(request.data)
-        serializer = VMSnapshotCreateSerializer(data=request.data)
-
-        serializer.gb_ssd = 12
-        serializer.gb_hdd = 120
-        print("F")
-        serializer.is_valid(raise_exception=True)
-
-        print(serializer)
-        print("A")
-        serializer.save()
-        print("B")
-
-
-        # snapshot = VMSnapshotProduct(owner=request.user,
-        #                              **serialzer.data)
-
-        return Response(serializer.data)

From 41a5eae8796876f847d87106fdddfc3612be65e7 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Thu, 27 Feb 2020 12:09:29 +0100
Subject: [PATCH 07/23] cleanup views/vmsnapshot

---
 uncloud/uncloud/urls.py     | 2 +-
 uncloud/uncloud_vm/views.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py
index 79958c5..a02f24a 100644
--- a/uncloud/uncloud/urls.py
+++ b/uncloud/uncloud/urls.py
@@ -25,7 +25,7 @@ from opennebula import views as oneviews
 router = routers.DefaultRouter()
 
 # user / regular urls
-router.register(r'vm/snapshot', vmviews.VMSnapshotProductView, basename='VMSnapshot')
+router.register(r'vm/snapshot', vmviews.VMSnapshotProductViewSet, basename='VMSnapshot')
 router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct')
 
 # Pay
diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py
index 55b607f..c82dff3 100644
--- a/uncloud/uncloud_vm/views.py
+++ b/uncloud/uncloud_vm/views.py
@@ -6,7 +6,7 @@ from django.shortcuts import get_object_or_404
 from rest_framework import viewsets, permissions
 from rest_framework.response import Response
 
-from .models import VMHost, VMProduct. VMSnapshotProduct
+from .models import VMHost, VMProduct, VMSnapshotProduct
 from uncloud_pay.models import Order
 
 from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer

From f358acca058ab55e51067dad2d9cfb02e441acfa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= <timothee.floure@posteo.net>
Date: Thu, 27 Feb 2020 12:10:26 +0100
Subject: [PATCH 08/23] Fix payment creation

---
 uncloud/uncloud_pay/serializers.py | 4 ++--
 uncloud/uncloud_pay/views.py       | 8 +++++---
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py
index e11544b..024fe3f 100644
--- a/uncloud/uncloud_pay/serializers.py
+++ b/uncloud/uncloud_pay/serializers.py
@@ -4,9 +4,9 @@ from .models import Bill, Payment
 class BillSerializer(serializers.ModelSerializer):
     class Meta:
         model = Bill
-        fields = ['user', 'amount']
+        fields = ['owner', 'amount']
 
 class PaymentSerializer(serializers.ModelSerializer):
     class Meta:
         model = Payment
-        fields = ['user', 'amount', 'source', 'timestamp']
+        fields = ['owner', 'amount', 'source', 'timestamp']
diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py
index 8fc02ea..8f37814 100644
--- a/uncloud/uncloud_pay/views.py
+++ b/uncloud/uncloud_pay/views.py
@@ -1,8 +1,10 @@
 from django.shortcuts import render
-from rest_framework import viewsets, permissions
+from rest_framework import viewsets, permissions, status
+from rest_framework.response import Response
 
 from .models import Bill, Payment
 from .serializers import BillSerializer, PaymentSerializer
+from datetime import datetime
 
 
 # to be implemented
@@ -35,12 +37,12 @@ class PaymentViewSet(viewsets.ModelViewSet):
     http_method_names = ['get', 'post']
 
     def get_queryset(self):
-        return Payment.objects.filter(user=self.request.user)
+        return Payment.objects.filter(owner=self.request.user)
 
     def create(self, request):
         serializer = self.get_serializer(data=request.data)
         serializer.is_valid(raise_exception=True)
-        serializer.save(user=request.user,timestamp=datetime.now())
+        serializer.save(owner=request.user,timestamp=datetime.now())
 
         headers = self.get_success_headers(serializer.data)
         return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

From b9b605f407ed53d26418c2475ef928504411d92e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= <timothee.floure@posteo.net>
Date: Thu, 27 Feb 2020 12:21:25 +0100
Subject: [PATCH 09/23] Add ADMIN endpoints for bills and payments

---
 uncloud/uncloud/urls.py            |  2 ++
 uncloud/uncloud_pay/serializers.py |  2 +-
 uncloud/uncloud_pay/views.py       | 39 +++++++++++++++++++++++++-----
 3 files changed, 36 insertions(+), 7 deletions(-)

diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py
index 79958c5..341f81a 100644
--- a/uncloud/uncloud/urls.py
+++ b/uncloud/uncloud/urls.py
@@ -33,6 +33,8 @@ router.register(r'bill', payviews.BillViewSet, basename='bill')
 router.register(r'payment', payviews.PaymentViewSet, basename='payment')
 
 # admin/staff urls
+router.register(r'admin/bill', payviews.AdminBillViewSet, basename='admin/bill')
+router.register(r'admin/payment', payviews.AdminPaymentViewSet, basename='admin/payment')
 router.register(r'admin/vmhost', vmviews.VMHostViewSet)
 router.register(r'admin/opennebula', oneviews.VMViewSet, basename='opennebula')
 router.register(r'admin/opennebula_raw', oneviews.RawVMViewSet)
diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py
index 024fe3f..f4fd565 100644
--- a/uncloud/uncloud_pay/serializers.py
+++ b/uncloud/uncloud_pay/serializers.py
@@ -4,7 +4,7 @@ from .models import Bill, Payment
 class BillSerializer(serializers.ModelSerializer):
     class Meta:
         model = Bill
-        fields = ['owner', 'amount']
+        fields = ['owner', 'amount', 'due_date', 'creation_date', 'starting_date', 'ending_date', 'paid']
 
 class PaymentSerializer(serializers.ModelSerializer):
     class Meta:
diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py
index 8f37814..d824d27 100644
--- a/uncloud/uncloud_pay/views.py
+++ b/uncloud/uncloud_pay/views.py
@@ -6,8 +6,9 @@ from .models import Bill, Payment
 from .serializers import BillSerializer, PaymentSerializer
 from datetime import datetime
 
+###
+# Standard user views:
 
-# to be implemented
 class BalanceViewSet(viewsets.ViewSet):
     # here we return a number
     # number = sum(payments) - sum(bills)
@@ -20,10 +21,9 @@ class BalanceViewSet(viewsets.ViewSet):
     pass
 
 
-class BillViewSet(viewsets.ModelViewSet):
+class BillViewSet(viewsets.ReadOnlyModelViewSet):
     serializer_class = BillSerializer
     permission_classes = [permissions.IsAuthenticated]
-    http_method_names = ['get']
 
     def get_queryset(self):
         return Bill.objects.filter(owner=self.request.user)
@@ -31,10 +31,19 @@ class BillViewSet(viewsets.ModelViewSet):
     def unpaid(self, request):
         return Bill.objects.filter(owner=self.request.user, paid=False)
 
-class PaymentViewSet(viewsets.ModelViewSet):
+class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
+    serializer_class = PaymentSerializer
+    permission_classes = [permissions.IsAuthenticated]
+
+    def get_queryset(self):
+        return Payment.objects.filter(owner=self.request.user)
+
+###
+# Admin views.
+
+class AdminPaymentViewSet(viewsets.ModelViewSet):
     serializer_class = PaymentSerializer
     permission_classes = [permissions.IsAuthenticated]
-    http_method_names = ['get', 'post']
 
     def get_queryset(self):
         return Payment.objects.filter(owner=self.request.user)
@@ -42,7 +51,25 @@ class PaymentViewSet(viewsets.ModelViewSet):
     def create(self, request):
         serializer = self.get_serializer(data=request.data)
         serializer.is_valid(raise_exception=True)
-        serializer.save(owner=request.user,timestamp=datetime.now())
+        serializer.save(timestamp=datetime.now())
+
+        headers = self.get_success_headers(serializer.data)
+        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
+
+class AdminBillViewSet(viewsets.ModelViewSet):
+    serializer_class = BillSerializer
+    permission_classes = [permissions.IsAuthenticated]
+
+    def get_queryset(self):
+        return Bill.objects.filter(owner=self.request.user)
+
+    def unpaid(self, request):
+        return Bill.objects.filter(owner=self.request.user, paid=False)
+
+    def create(self, request):
+        serializer = self.get_serializer(data=request.data)
+        serializer.is_valid(raise_exception=True)
+        serializer.save(created_at=datetime.now())
 
         headers = self.get_success_headers(serializer.data)
         return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

From 225f20c91b423b2b4a04a7cb83e7d2df03258047 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= <timothee.floure@posteo.net>
Date: Thu, 27 Feb 2020 12:21:52 +0100
Subject: [PATCH 10/23] Fix typo in payment source model

---
 uncloud/uncloud_pay/models.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/uncloud/uncloud_pay/models.py b/uncloud/uncloud_pay/models.py
index 71653fa..6a33fd5 100644
--- a/uncloud/uncloud_pay/models.py
+++ b/uncloud/uncloud_pay/models.py
@@ -86,7 +86,7 @@ class Payment(models.Model):
     source = models.CharField(max_length=256,
                               choices = (
                                   ('wire', 'Wire Transfer'),
-                                  ('strip', 'Stripe'),
+                                  ('stripe', 'Stripe'),
                                   ('voucher', 'Voucher'),
                                   ('referral', 'Referral'),
                                   ('unknown', 'Unknown')

From a9aac394866a5df90ac0c3945a5d749631cdc7b0 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Thu, 27 Feb 2020 12:31:20 +0100
Subject: [PATCH 11/23] Create a vmsnapshot + associated order

---
 uncloud/uncloud/urls.py           |  2 +-
 uncloud/uncloud_vm/serializers.py |  7 +------
 uncloud/uncloud_vm/views.py       | 21 ++++++++++++++++++---
 3 files changed, 20 insertions(+), 10 deletions(-)

diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py
index a02f24a..d6d3b7d 100644
--- a/uncloud/uncloud/urls.py
+++ b/uncloud/uncloud/urls.py
@@ -25,7 +25,7 @@ from opennebula import views as oneviews
 router = routers.DefaultRouter()
 
 # user / regular urls
-router.register(r'vm/snapshot', vmviews.VMSnapshotProductViewSet, basename='VMSnapshot')
+router.register(r'vm/snapshot', vmviews.VMSnapshotProductViewSet, basename='vmsnapshotproduct')
 router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct')
 
 # Pay
diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py
index 232e954..c1eafe2 100644
--- a/uncloud/uncloud_vm/serializers.py
+++ b/uncloud/uncloud_vm/serializers.py
@@ -14,12 +14,7 @@ class VMProductSerializer(serializers.HyperlinkedModelSerializer):
         model = VMProduct
         fields = '__all__'
 
-class VMSnapshotProductSerializer(serializers.HyperlinkedModelSerializer):
-    class Meta:
-        model = VMSnapshotProduct
-        fields = ['uuid', 'status', 'recurring_price', 'one_time_price' ]
-
-class VMSnapshotProductCreateSerializer(serializers.HyperlinkedModelSerializer):
+class VMSnapshotProductSerializer(serializers.ModelSerializer):
     class Meta:
         model = VMSnapshotProduct
         fields = '__all__'
diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py
index c82dff3..53986b4 100644
--- a/uncloud/uncloud_vm/views.py
+++ b/uncloud/uncloud_vm/views.py
@@ -11,6 +11,8 @@ from uncloud_pay.models import Order
 
 from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer
 
+import datetime
+
 class VMHostViewSet(viewsets.ModelViewSet):
     serializer_class = VMHostSerializer
     queryset = VMHost.objects.all()
@@ -40,12 +42,25 @@ class VMSnapshotProductViewSet(viewsets.ModelViewSet):
         return VMSnapshotProduct.objects.filter(owner=self.request.user)
 
     def create(self, request):
-        serializer = VMProductSerializer(data=request.data, context={'request': request})
+        serializer = VMSnapshotProductSerializer(data=request.data, context={'request': request})
         serializer.is_valid(raise_exception=True)
 
+        print(serializer)
         # Create order
-        #order = Order()
+        now = datetime.datetime.now()
+        order = Order(owner=request.user,
+                      creation_date=now,
+                      starting_date=now,
+                      recurring_price=20,
+                      one_time_price=0,
+                      recurring_period="per_month")
+        order.save()
+        print(order)
 
-        serializer.save(owner=request.user)
+        # FIXME: calculate the gb_* values
+        serializer.save(owner=request.user,
+                        order=order,
+                        gb_ssd=12,
+                        gb_hdd=20)
 
         return Response(serializer.data)

From 7bf4f2adb22e8665d87a7b011f85ba0368bd43e4 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Thu, 27 Feb 2020 12:36:33 +0100
Subject: [PATCH 12/23] --debug

Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
---
 uncloud/uncloud_vm/views.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py
index 53986b4..444d134 100644
--- a/uncloud/uncloud_vm/views.py
+++ b/uncloud/uncloud_vm/views.py
@@ -45,7 +45,6 @@ class VMSnapshotProductViewSet(viewsets.ModelViewSet):
         serializer = VMSnapshotProductSerializer(data=request.data, context={'request': request})
         serializer.is_valid(raise_exception=True)
 
-        print(serializer)
         # Create order
         now = datetime.datetime.now()
         order = Order(owner=request.user,
@@ -55,7 +54,6 @@ class VMSnapshotProductViewSet(viewsets.ModelViewSet):
                       one_time_price=0,
                       recurring_period="per_month")
         order.save()
-        print(order)
 
         # FIXME: calculate the gb_* values
         serializer.save(owner=request.user,

From f5eadd6ddbbcbd08a65a1beb8f3796bb6aa5a9ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= <timothee.floure@posteo.net>
Date: Thu, 27 Feb 2020 12:38:04 +0100
Subject: [PATCH 13/23] Move user view to uncloud_pay

---
 uncloud/uncloud/urls.py            |  1 +
 uncloud/uncloud_api/views.py       |  6 +++++-
 uncloud/uncloud_pay/serializers.py |  9 +++++++++
 uncloud/uncloud_pay/views.py       | 15 ++++++++++++++-
 4 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py
index 341f81a..358e4c7 100644
--- a/uncloud/uncloud/urls.py
+++ b/uncloud/uncloud/urls.py
@@ -29,6 +29,7 @@ router.register(r'vm/snapshot', vmviews.VMSnapshotProductView, basename='VMSnaps
 router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct')
 
 # Pay
+router.register(r'user', payviews.UserViewSet, basename='user')
 router.register(r'bill', payviews.BillViewSet, basename='bill')
 router.register(r'payment', payviews.PaymentViewSet, basename='payment')
 
diff --git a/uncloud/uncloud_api/views.py b/uncloud/uncloud_api/views.py
index c90b963..18cc324 100644
--- a/uncloud/uncloud_api/views.py
+++ b/uncloud/uncloud_api/views.py
@@ -43,9 +43,13 @@ import re
 #         return Response(products)
 
 
-class UserViewSet(viewsets.ModelViewSet):
+class UserViewSet(viewsets.ReadOnlyModelViewSet):
     serializer_class = UserSerializer
     permission_classes = [permissions.IsAuthenticated]
 
     def get_queryset(self):
         return self.request.user
+
+    @action(detail=True)
+    def balance(self, request):
+        return Response(status=status.HTTP_204_NO_CONTENT)
diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py
index f4fd565..5bb22ec 100644
--- a/uncloud/uncloud_pay/serializers.py
+++ b/uncloud/uncloud_pay/serializers.py
@@ -1,3 +1,4 @@
+from django.contrib.auth import get_user_model
 from rest_framework import serializers
 from .models import Bill, Payment
 
@@ -10,3 +11,11 @@ class PaymentSerializer(serializers.ModelSerializer):
     class Meta:
         model = Payment
         fields = ['owner', 'amount', 'source', 'timestamp']
+
+class UserSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = get_user_model()
+        fields = ['username', 'email']
+
+    def get_balance(self, obj):
+        return 666
diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py
index d824d27..5111f6c 100644
--- a/uncloud/uncloud_pay/views.py
+++ b/uncloud/uncloud_pay/views.py
@@ -1,9 +1,11 @@
 from django.shortcuts import render
+from django.contrib.auth import get_user_model
 from rest_framework import viewsets, permissions, status
 from rest_framework.response import Response
+from rest_framework.decorators import action
 
 from .models import Bill, Payment
-from .serializers import BillSerializer, PaymentSerializer
+from .serializers import BillSerializer, PaymentSerializer, UserSerializer
 from datetime import datetime
 
 ###
@@ -38,6 +40,17 @@ class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
     def get_queryset(self):
         return Payment.objects.filter(owner=self.request.user)
 
+class UserViewSet(viewsets.ReadOnlyModelViewSet):
+    serializer_class = UserSerializer
+    permission_classes = [permissions.IsAuthenticated]
+
+    def get_queryset(self):
+        return get_user_model().objects.all()
+
+    @action(detail=True)
+    def balance(self, request):
+        return Response(status=status.HTTP_204_NO_CONTENT)
+
 ###
 # Admin views.
 

From b722f30ea5048a305d627a083d0abfd310fa8092 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Thu, 27 Feb 2020 12:42:09 +0100
Subject: [PATCH 14/23] ++doc

---
 uncloud/README.md | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/uncloud/README.md b/uncloud/README.md
index 19896d9..1e71f6f 100644
--- a/uncloud/README.md
+++ b/uncloud/README.md
@@ -76,3 +76,12 @@ sample values with real values.
 
 
 ### Creating a VM Snapshot
+
+
+## Working Beta APIs
+
+### Snapshotting
+
+```
+http -a nicoschottelius:$(pass ungleich.ch/nico.schottelius@ungleich.ch) http://localhost:8000/vm/snapshot/  vm_uuid=$(uuidgen)
+```

From 1ff5702ce3cd217f2fd26442c76a466fe558d1ec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= <timothee.floure@posteo.net>
Date: Thu, 27 Feb 2020 12:42:24 +0100
Subject: [PATCH 15/23] Expose Order model

---
 uncloud/uncloud/urls.py            |  2 ++
 uncloud/uncloud_pay/serializers.py | 11 +++++++++--
 uncloud/uncloud_pay/views.py       | 22 ++++++++++++++++++----
 3 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py
index 358e4c7..9ea7c6a 100644
--- a/uncloud/uncloud/urls.py
+++ b/uncloud/uncloud/urls.py
@@ -31,11 +31,13 @@ router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct')
 # Pay
 router.register(r'user', payviews.UserViewSet, basename='user')
 router.register(r'bill', payviews.BillViewSet, basename='bill')
+router.register(r'order', payviews.OrderViewSet, basename='order')
 router.register(r'payment', payviews.PaymentViewSet, basename='payment')
 
 # admin/staff urls
 router.register(r'admin/bill', payviews.AdminBillViewSet, basename='admin/bill')
 router.register(r'admin/payment', payviews.AdminPaymentViewSet, basename='admin/payment')
+router.register(r'admin/order', payviews.AdminOrderViewSet, basename='admin/order')
 router.register(r'admin/vmhost', vmviews.VMHostViewSet)
 router.register(r'admin/opennebula', oneviews.VMViewSet, basename='opennebula')
 router.register(r'admin/opennebula_raw', oneviews.RawVMViewSet)
diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py
index 5bb22ec..be00a0c 100644
--- a/uncloud/uncloud_pay/serializers.py
+++ b/uncloud/uncloud_pay/serializers.py
@@ -1,17 +1,24 @@
 from django.contrib.auth import get_user_model
 from rest_framework import serializers
-from .models import Bill, Payment
+from .models import Bill, Payment, Order
 
 class BillSerializer(serializers.ModelSerializer):
     class Meta:
         model = Bill
-        fields = ['owner', 'amount', 'due_date', 'creation_date', 'starting_date', 'ending_date', 'paid']
+        fields = ['owner', 'amount', 'due_date', 'creation_date',
+                'starting_date', 'ending_date', 'paid']
 
 class PaymentSerializer(serializers.ModelSerializer):
     class Meta:
         model = Payment
         fields = ['owner', 'amount', 'source', 'timestamp']
 
+class OrderSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Order
+        fields = ['owner', 'creation_date', 'starting_date', 'ending_date',
+                'bill', 'recurring_price', 'one_time_price', 'recurring_period']
+
 class UserSerializer(serializers.ModelSerializer):
     class Meta:
         model = get_user_model()
diff --git a/uncloud/uncloud_pay/views.py b/uncloud/uncloud_pay/views.py
index 5111f6c..ae88861 100644
--- a/uncloud/uncloud_pay/views.py
+++ b/uncloud/uncloud_pay/views.py
@@ -4,8 +4,8 @@ from rest_framework import viewsets, permissions, status
 from rest_framework.response import Response
 from rest_framework.decorators import action
 
-from .models import Bill, Payment
-from .serializers import BillSerializer, PaymentSerializer, UserSerializer
+from .models import Bill, Payment, Order
+from .serializers import BillSerializer, PaymentSerializer, UserSerializer, OrderSerializer
 from datetime import datetime
 
 ###
@@ -40,6 +40,13 @@ class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
     def get_queryset(self):
         return Payment.objects.filter(owner=self.request.user)
 
+class OrderViewSet(viewsets.ReadOnlyModelViewSet):
+    serializer_class = OrderSerializer
+    permission_classes = [permissions.IsAuthenticated]
+
+    def get_queryset(self):
+        return Order.objects.filter(owner=self.request.user)
+
 class UserViewSet(viewsets.ReadOnlyModelViewSet):
     serializer_class = UserSerializer
     permission_classes = [permissions.IsAuthenticated]
@@ -59,7 +66,7 @@ class AdminPaymentViewSet(viewsets.ModelViewSet):
     permission_classes = [permissions.IsAuthenticated]
 
     def get_queryset(self):
-        return Payment.objects.filter(owner=self.request.user)
+        return Payment.objects.all()
 
     def create(self, request):
         serializer = self.get_serializer(data=request.data)
@@ -74,7 +81,7 @@ class AdminBillViewSet(viewsets.ModelViewSet):
     permission_classes = [permissions.IsAuthenticated]
 
     def get_queryset(self):
-        return Bill.objects.filter(owner=self.request.user)
+        return Bill.objects.all()
 
     def unpaid(self, request):
         return Bill.objects.filter(owner=self.request.user, paid=False)
@@ -86,3 +93,10 @@ class AdminBillViewSet(viewsets.ModelViewSet):
 
         headers = self.get_success_headers(serializer.data)
         return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
+
+class AdminOrderViewSet(viewsets.ModelViewSet):
+    serializer_class = OrderSerializer
+    permission_classes = [permissions.IsAuthenticated]
+
+    def get_queryset(self):
+        return Order.objects.all()

From 70a4fe4d9008b5f6be753d1130e6d331ba457d6f Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Thu, 27 Feb 2020 12:45:54 +0100
Subject: [PATCH 16/23] order: serialize all fields

---
 uncloud/README.md                  | 6 ++++++
 uncloud/uncloud_pay/serializers.py | 3 +--
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/uncloud/README.md b/uncloud/README.md
index b1d98a5..390a3af 100644
--- a/uncloud/README.md
+++ b/uncloud/README.md
@@ -82,6 +82,12 @@ sample values with real values.
 
 These APIs can be used for internal testing.
 
+### URL Overview
+
+```
+http -a nicoschottelius:$(pass ungleich.ch/nico.schottelius@ungleich.ch) http://localhost:8000
+```
+
 ### Snapshotting
 
 ```
diff --git a/uncloud/uncloud_pay/serializers.py b/uncloud/uncloud_pay/serializers.py
index be00a0c..130f683 100644
--- a/uncloud/uncloud_pay/serializers.py
+++ b/uncloud/uncloud_pay/serializers.py
@@ -16,8 +16,7 @@ class PaymentSerializer(serializers.ModelSerializer):
 class OrderSerializer(serializers.ModelSerializer):
     class Meta:
         model = Order
-        fields = ['owner', 'creation_date', 'starting_date', 'ending_date',
-                'bill', 'recurring_price', 'one_time_price', 'recurring_period']
+        fields = '__all__'
 
 class UserSerializer(serializers.ModelSerializer):
     class Meta:

From bd6008462d9eb6823f9db90ed8ee895d474cb1a0 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Thu, 27 Feb 2020 15:29:05 +0100
Subject: [PATCH 17/23] add template for uncloud_net

Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
---
 uncloud/uncloud_net/__init__.py            | 0
 uncloud/uncloud_net/admin.py               | 3 +++
 uncloud/uncloud_net/apps.py                | 5 +++++
 uncloud/uncloud_net/migrations/__init__.py | 0
 uncloud/uncloud_net/models.py              | 3 +++
 uncloud/uncloud_net/tests.py               | 3 +++
 uncloud/uncloud_net/views.py               | 3 +++
 7 files changed, 17 insertions(+)
 create mode 100644 uncloud/uncloud_net/__init__.py
 create mode 100644 uncloud/uncloud_net/admin.py
 create mode 100644 uncloud/uncloud_net/apps.py
 create mode 100644 uncloud/uncloud_net/migrations/__init__.py
 create mode 100644 uncloud/uncloud_net/models.py
 create mode 100644 uncloud/uncloud_net/tests.py
 create mode 100644 uncloud/uncloud_net/views.py

diff --git a/uncloud/uncloud_net/__init__.py b/uncloud/uncloud_net/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/uncloud/uncloud_net/admin.py b/uncloud/uncloud_net/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/uncloud/uncloud_net/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/uncloud/uncloud_net/apps.py b/uncloud/uncloud_net/apps.py
new file mode 100644
index 0000000..489beb1
--- /dev/null
+++ b/uncloud/uncloud_net/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class UncloudNetConfig(AppConfig):
+    name = 'uncloud_net'
diff --git a/uncloud/uncloud_net/migrations/__init__.py b/uncloud/uncloud_net/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/uncloud/uncloud_net/models.py b/uncloud/uncloud_net/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/uncloud/uncloud_net/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/uncloud/uncloud_net/tests.py b/uncloud/uncloud_net/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/uncloud/uncloud_net/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/uncloud/uncloud_net/views.py b/uncloud/uncloud_net/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/uncloud/uncloud_net/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.

From 288a65f2192a93aaa2660b244be67b9bb8faee4a Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Thu, 27 Feb 2020 15:29:15 +0100
Subject: [PATCH 18/23] ++update

Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
---
 .../migrations/0002_auto_20200227_1230.py     | 18 ++++++++++
 .../migrations/0005_auto_20200227_1230.py     | 36 +++++++++++++++++++
 uncloud/uncloud_vm/models.py                  | 15 ++++----
 uncloud/uncloud_vm/serializers.py             | 10 ++++++
 uncloud/uncloud_vm/views.py                   | 13 ++++++-
 5 files changed, 83 insertions(+), 9 deletions(-)
 create mode 100644 uncloud/uncloud_pay/migrations/0002_auto_20200227_1230.py
 create mode 100644 uncloud/uncloud_vm/migrations/0005_auto_20200227_1230.py

diff --git a/uncloud/uncloud_pay/migrations/0002_auto_20200227_1230.py b/uncloud/uncloud_pay/migrations/0002_auto_20200227_1230.py
new file mode 100644
index 0000000..0643e9a
--- /dev/null
+++ b/uncloud/uncloud_pay/migrations/0002_auto_20200227_1230.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.0.3 on 2020-02-27 12:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('uncloud_pay', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='payment',
+            name='source',
+            field=models.CharField(choices=[('wire', 'Wire Transfer'), ('stripe', 'Stripe'), ('voucher', 'Voucher'), ('referral', 'Referral'), ('unknown', 'Unknown')], default='unknown', max_length=256),
+        ),
+    ]
diff --git a/uncloud/uncloud_vm/migrations/0005_auto_20200227_1230.py b/uncloud/uncloud_vm/migrations/0005_auto_20200227_1230.py
new file mode 100644
index 0000000..1bd711b
--- /dev/null
+++ b/uncloud/uncloud_vm/migrations/0005_auto_20200227_1230.py
@@ -0,0 +1,36 @@
+# Generated by Django 3.0.3 on 2020-02-27 12:30
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('uncloud_pay', '0002_auto_20200227_1230'),
+        ('uncloud_vm', '0004_vmsnapshotproduct'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='vmsnapshotproduct',
+            name='vm_uuid',
+        ),
+        migrations.AddField(
+            model_name='vmproduct',
+            name='order',
+            field=models.ForeignKey(default=0, editable=False, on_delete=django.db.models.deletion.CASCADE, to='uncloud_pay.Order'),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='vmproduct',
+            name='status',
+            field=models.CharField(choices=[('pending', 'Pending'), ('being_created', 'Being created'), ('active', 'Active'), ('deleted', 'Deleted')], default='pending', max_length=256),
+        ),
+        migrations.AddField(
+            model_name='vmsnapshotproduct',
+            name='vm',
+            field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, to='uncloud_vm.VMProduct'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/uncloud/uncloud_vm/models.py b/uncloud/uncloud_vm/models.py
index 12d188e..4ebae25 100644
--- a/uncloud/uncloud_vm/models.py
+++ b/uncloud/uncloud_vm/models.py
@@ -32,13 +32,7 @@ class VMHost(models.Model):
     )
 
 
-class VMProduct(models.Model):
-    uuid = models.UUIDField(primary_key=True,
-                            default=uuid.uuid4,
-                            editable=False)
-    owner = models.ForeignKey(get_user_model(),
-                              on_delete=models.CASCADE,
-                              editable=False)
+class VMProduct(Product):
     vmhost   = models.ForeignKey(VMHost,
                                  on_delete=models.CASCADE,
                                  editable=False,
@@ -72,8 +66,12 @@ class OperatingSystemDisk(VMDiskProduct):
 
 class VMNetworkCard(models.Model):
     vm   = models.ForeignKey(VMProduct, on_delete=models.CASCADE)
+
     mac_address = models.IntegerField()
 
+    ip_address = models.GenericIPAddressField(blank=True,
+                                              null=True)
+
 
 class VMSnapshotProduct(Product):
     price_per_gb_ssd = 0.35
@@ -83,7 +81,8 @@ class VMSnapshotProduct(Product):
     gb_ssd = models.FloatField(editable=False)
     gb_hdd = models.FloatField(editable=False)
 
-    vm_uuid = models.UUIDField()
+    vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE)
+    #vm_uuid = models.UUIDField()
 
     # Need to setup recurring_price and one_time_price and recurring period
 
diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py
index c1eafe2..b247709 100644
--- a/uncloud/uncloud_vm/serializers.py
+++ b/uncloud/uncloud_vm/serializers.py
@@ -14,7 +14,17 @@ class VMProductSerializer(serializers.HyperlinkedModelSerializer):
         model = VMProduct
         fields = '__all__'
 
+
+#    def create(self, validated_data):
+#        return VMSnapshotProduct()
+
 class VMSnapshotProductSerializer(serializers.ModelSerializer):
     class Meta:
         model = VMSnapshotProduct
         fields = '__all__'
+
+
+    # verify that vm.owner == user.request
+    def validate_vm(self, value):
+        print(value)
+        return True
diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py
index 444d134..7e517f5 100644
--- a/uncloud/uncloud_vm/views.py
+++ b/uncloud/uncloud_vm/views.py
@@ -11,6 +11,7 @@ from uncloud_pay.models import Order
 
 from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer
 
+
 import datetime
 
 class VMHostViewSet(viewsets.ModelViewSet):
@@ -29,7 +30,17 @@ class VMProductViewSet(viewsets.ModelViewSet):
     def create(self, request):
         serializer = VMProductSerializer(data=request.data, context={'request': request})
         serializer.is_valid(raise_exception=True)
-        serializer.save(owner=request.user)
+        # Create order
+        now = datetime.datetime.now()
+        order = Order(owner=request.user,
+                      creation_date=now,
+                      starting_date=now,
+                      recurring_price=20,
+                      one_time_price=0,
+                      recurring_period="per_month")
+        order.save()
+
+        serializer.save(owner=request.user, order=order)
 
         return Response(serializer.data)
 

From 89215e47b6daba0b860eb3d389ec3b1109231dde Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Fri, 28 Feb 2020 09:34:29 +0100
Subject: [PATCH 19/23] phase in mac

---
 uncloud/uncloud_net/models.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/uncloud/uncloud_net/models.py b/uncloud/uncloud_net/models.py
index 71a8362..6d0c742 100644
--- a/uncloud/uncloud_net/models.py
+++ b/uncloud/uncloud_net/models.py
@@ -1,3 +1,4 @@
 from django.db import models
 
-# Create your models here.
+class MACAdress(models.Model):
+    prefix = 0x420000000000

From bcbd6f6f8339e7489be0b7e126df6f208dd8465a Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Sat, 29 Feb 2020 16:45:52 +0100
Subject: [PATCH 20/23] Introduce disk->image relationship

Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
---
 uncloud/uncloud/urls.py                       |  8 ++
 .../migrations/0006_auto_20200229_1545.py     | 53 ++++++++++++
 uncloud/uncloud_vm/models.py                  | 86 ++++++++++---------
 uncloud/uncloud_vm/serializers.py             | 34 ++++++--
 uncloud/uncloud_vm/views.py                   | 36 ++++++--
 5 files changed, 161 insertions(+), 56 deletions(-)
 create mode 100644 uncloud/uncloud_vm/migrations/0006_auto_20200229_1545.py

diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py
index 5ee9f07..40b3b20 100644
--- a/uncloud/uncloud/urls.py
+++ b/uncloud/uncloud/urls.py
@@ -26,8 +26,16 @@ router = routers.DefaultRouter()
 
 # user / regular urls
 router.register(r'vm/snapshot', vmviews.VMSnapshotProductViewSet, basename='vmsnapshotproduct')
+router.register(r'vm/image/mine', vmviews.VMDiskImageProductMineViewSet, basename='vmdiskimagemineproduct')
+router.register(r'vm/image/public', vmviews.VMDiskImageProductPublicViewSet, basename='vmdiskimagepublicproduct')
+
+
+#router.register(r'vm/disk', vmviews.VMDiskProductViewSet, basename='vmdiskproduct')
+
 router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct')
 
+
+
 # Pay
 router.register(r'user', payviews.UserViewSet, basename='user')
 router.register(r'bill', payviews.BillViewSet, basename='bill')
diff --git a/uncloud/uncloud_vm/migrations/0006_auto_20200229_1545.py b/uncloud/uncloud_vm/migrations/0006_auto_20200229_1545.py
new file mode 100644
index 0000000..208aeaa
--- /dev/null
+++ b/uncloud/uncloud_vm/migrations/0006_auto_20200229_1545.py
@@ -0,0 +1,53 @@
+# Generated by Django 3.0.3 on 2020-02-29 15:45
+
+from django.conf import settings
+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_vm', '0005_auto_20200227_1230'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='VMDiskImageProduct',
+            fields=[
+                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+                ('name', models.CharField(max_length=256)),
+                ('is_os_image', models.BooleanField(default=False)),
+                ('is_public', models.BooleanField(default=False)),
+                ('size_in_gb', models.FloatField()),
+                ('storage_class', models.CharField(choices=[('hdd', 'HDD'), ('ssd', 'SSD')], default='ssd', max_length=32)),
+                ('owner', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+        migrations.RemoveField(
+            model_name='vmdiskproduct',
+            name='storage_class',
+        ),
+        migrations.AddField(
+            model_name='vmdiskproduct',
+            name='owner',
+            field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='vmnetworkcard',
+            name='ip_address',
+            field=models.GenericIPAddressField(blank=True, null=True),
+        ),
+        migrations.DeleteModel(
+            name='OperatingSystemDisk',
+        ),
+        migrations.AddField(
+            model_name='vmdiskproduct',
+            name='image',
+            field=models.ForeignKey(default=0, on_delete=django.db.models.deletion.CASCADE, to='uncloud_vm.VMDiskImageProduct'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/uncloud/uncloud_vm/models.py b/uncloud/uncloud_vm/models.py
index 4ebae25..b585cb9 100644
--- a/uncloud/uncloud_vm/models.py
+++ b/uncloud/uncloud_vm/models.py
@@ -46,11 +46,27 @@ class VMProduct(Product):
 class VMWithOSProduct(VMProduct):
     pass
 
-class VMDiskProduct(models.Model):
+
+class VMDiskImageProduct(models.Model):
+    """
+    Images are used for cloning/linking.
+
+    They are the base for images.
+
+    """
+
     uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
-    vm   = models.ForeignKey(VMProduct, on_delete=models.CASCADE)
+    owner = models.ForeignKey(get_user_model(),
+                              on_delete=models.CASCADE,
+                              editable=False)
+
+    name = models.CharField(max_length=256)
+    is_os_image = models.BooleanField(default=False)
+    is_public = models.BooleanField(default=False)
+
     size_in_gb = models.FloatField()
 
+
     storage_class = models.CharField(max_length=32,
                               choices = (
                                   ('hdd', 'HDD'),
@@ -59,9 +75,32 @@ class VMDiskProduct(models.Model):
                               default='ssd'
     )
 
-class OperatingSystemDisk(VMDiskProduct):
-    """ Defines an Operating System Disk that can be cloned for a VM """
-    os_name = models.CharField(max_length=128)
+    # source = models.CharField(max_length=32,
+    #                           choices = (
+    #                               ('url', 'HDD'),
+    #                               ('ssd', 'SSD'),
+    #                           ),
+    #                           default='ssd'
+    # )
+
+class VMDiskProduct(models.Model):
+    """
+    The VMDiskProduct is attached to a VM.
+
+    It is based on a VMDiskImageProduct that will be used as a basis.
+
+    It can be enlarged, but not shrinked compared to the VMDiskImageProduct.
+    """
+
+    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
+    owner = models.ForeignKey(get_user_model(),
+                              on_delete=models.CASCADE,
+                              editable=False)
+
+    vm   = models.ForeignKey(VMProduct, on_delete=models.CASCADE)
+    image = models.ForeignKey(VMDiskImageProduct, on_delete=models.CASCADE)
+
+    size_in_gb = models.FloatField()
 
 
 class VMNetworkCard(models.Model):
@@ -74,44 +113,7 @@ class VMNetworkCard(models.Model):
 
 
 class VMSnapshotProduct(Product):
-    price_per_gb_ssd = 0.35
-    price_per_gb_hdd = 1.5/100
-
-    # This we need to get from the VM
     gb_ssd = models.FloatField(editable=False)
     gb_hdd = models.FloatField(editable=False)
 
     vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE)
-    #vm_uuid = models.UUIDField()
-
-    # Need to setup recurring_price and one_time_price and recurring period
-
-    sample_ssd = 10
-    sample_hdd = 100
-
-    def recurring_price(self):
-        return 0
-
-    def one_time_price(self):
-        return 0
-
-    @classmethod
-    def sample_price(cls):
-        return cls.sample_ssd * cls.price_per_gb_ssd + cls.sample_hdd * cls.price_per_gb_hdd
-
-    description = "Create snapshot of a VM"
-    recurring_period = "monthly"
-
-    @classmethod
-    def pricing_model(cls):
-        return """
-Pricing is on monthly basis and storage prices are equivalent to the storage
-price in the VM.
-
-Price per GB SSD is: {}
-Price per GB HDD is: {}
-
-
-Sample price for a VM with {} GB SSD and {} GB HDD VM is: {}.
-""".format(cls.price_per_gb_ssd, cls.price_per_gb_hdd,
-           cls.sample_ssd, cls.sample_hdd, cls.sample_price())
diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py
index b247709..a64fdd0 100644
--- a/uncloud/uncloud_vm/serializers.py
+++ b/uncloud/uncloud_vm/serializers.py
@@ -1,22 +1,28 @@
 from django.contrib.auth import get_user_model
 
 from rest_framework import serializers
-from .models import VMHost, VMProduct, VMSnapshotProduct
+from .models import VMHost, VMProduct, VMSnapshotProduct, VMDiskProduct, VMDiskImageProduct
 
-class VMHostSerializer(serializers.HyperlinkedModelSerializer):
+class VMHostSerializer(serializers.ModelSerializer):
     class Meta:
         model = VMHost
         fields = '__all__'
 
 
-class VMProductSerializer(serializers.HyperlinkedModelSerializer):
+class VMProductSerializer(serializers.ModelSerializer):
     class Meta:
         model = VMProduct
         fields = '__all__'
 
+class VMDiskProductSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = VMDiskProduct
+        fields = '__all__'
 
-#    def create(self, validated_data):
-#        return VMSnapshotProduct()
+class VMDiskImageProductSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = VMDiskImageProduct
+        fields = '__all__'
 
 class VMSnapshotProductSerializer(serializers.ModelSerializer):
     class Meta:
@@ -26,5 +32,19 @@ class VMSnapshotProductSerializer(serializers.ModelSerializer):
 
     # verify that vm.owner == user.request
     def validate_vm(self, value):
-        print(value)
-        return True
+
+        if not value.owner == self.context['request'].user:
+            raise serializers.ValidationError("VM {} not found for owner {}.".format(value,
+                                                                                     self.context['request'].user))
+
+        disks = VMDiskProduct.objects.filter(vm=value)
+
+        if len(disks) == 0:
+            raise serializers.ValidationError("VM {} does not have any disks, cannot snapshot".format(value.uuid))
+
+        return value
+
+    pricing = {}
+    pricing['per_gb_ssd'] = 0.012
+    pricing['per_gb_hdd'] = 0.0006
+    pricing['recurring_period'] = 'per_day'
diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py
index 7e517f5..b9d80f9 100644
--- a/uncloud/uncloud_vm/views.py
+++ b/uncloud/uncloud_vm/views.py
@@ -6,10 +6,10 @@ from django.shortcuts import get_object_or_404
 from rest_framework import viewsets, permissions
 from rest_framework.response import Response
 
-from .models import VMHost, VMProduct, VMSnapshotProduct
+from .models import VMHost, VMProduct, VMSnapshotProduct, VMDiskProduct, VMDiskImageProduct
 from uncloud_pay.models import Order
 
-from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer
+from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer, VMDiskImageProductSerializer
 
 
 import datetime
@@ -19,6 +19,20 @@ class VMHostViewSet(viewsets.ModelViewSet):
     queryset = VMHost.objects.all()
     permission_classes = [permissions.IsAdminUser]
 
+class VMDiskImageProductMineViewSet(viewsets.ModelViewSet):
+    permission_classes = [permissions.IsAuthenticated]
+    serializer_class = VMDiskImageProductSerializer
+
+    def get_queryset(self):
+        return VMDiskImageProduct.objects.filter(owner=self.request.user)
+
+class VMDiskImageProductPublicViewSet(viewsets.ModelViewSet):
+    permission_classes = [permissions.IsAuthenticated]
+    serializer_class = VMDiskImageProductSerializer
+
+    def get_queryset(self):
+        return VMDiskImageProduct.objects.filter(is_public=True)
+
 
 class VMProductViewSet(viewsets.ModelViewSet):
     permission_classes = [permissions.IsAuthenticated]
@@ -54,22 +68,30 @@ class VMSnapshotProductViewSet(viewsets.ModelViewSet):
 
     def create(self, request):
         serializer = VMSnapshotProductSerializer(data=request.data, context={'request': request})
+
+        # This verifies that the VM belongs to the request user
         serializer.is_valid(raise_exception=True)
 
+        disks = VMDiskProduct.objects.filter(vm=serializer.validated_data['vm'])
+        ssds_size = sum([d.size_in_gb for d in disks if d.storage_class == 'ssd'])
+        hdds_size = sum([d.size_in_gb for d in disks if d.storage_class == 'hdd'])
+
+        recurring_price = serializer.pricing['per_gb_ssd'] * ssds_size + serializer.pricing['per_gb_hdd'] * hdds_size
+        recurring_period = serializer.pricing['recurring_period']
+
         # Create order
         now = datetime.datetime.now()
         order = Order(owner=request.user,
                       creation_date=now,
                       starting_date=now,
-                      recurring_price=20,
+                      recurring_price=recurring_price,
                       one_time_price=0,
-                      recurring_period="per_month")
+                      recurring_period=recurring_period)
         order.save()
 
-        # FIXME: calculate the gb_* values
         serializer.save(owner=request.user,
                         order=order,
-                        gb_ssd=12,
-                        gb_hdd=20)
+                        gb_ssd=ssds_size,
+                        gb_hdd=hdds_size)
 
         return Response(serializer.data)

From 6a38e4e0a44576c829685ed503cbca61cbc5b1f5 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Sat, 29 Feb 2020 17:00:13 +0100
Subject: [PATCH 21/23] add url for importing disk image

Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
---
 .../migrations/0007_auto_20200229_1559.py     | 23 +++++++++++++++++++
 uncloud/uncloud_vm/models.py                  |  9 ++++++--
 uncloud/uncloud_vm/serializers.py             |  9 ++++++--
 uncloud/uncloud_vm/views.py                   | 10 ++++++--
 4 files changed, 45 insertions(+), 6 deletions(-)
 create mode 100644 uncloud/uncloud_vm/migrations/0007_auto_20200229_1559.py

diff --git a/uncloud/uncloud_vm/migrations/0007_auto_20200229_1559.py b/uncloud/uncloud_vm/migrations/0007_auto_20200229_1559.py
new file mode 100644
index 0000000..6e08c0c
--- /dev/null
+++ b/uncloud/uncloud_vm/migrations/0007_auto_20200229_1559.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.0.3 on 2020-02-29 15:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('uncloud_vm', '0006_auto_20200229_1545'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='vmdiskimageproduct',
+            name='import_url',
+            field=models.URLField(blank=True, null=True),
+        ),
+        migrations.AlterField(
+            model_name='vmdiskimageproduct',
+            name='size_in_gb',
+            field=models.FloatField(blank=True, null=True),
+        ),
+    ]
diff --git a/uncloud/uncloud_vm/models.py b/uncloud/uncloud_vm/models.py
index b585cb9..f2cbf13 100644
--- a/uncloud/uncloud_vm/models.py
+++ b/uncloud/uncloud_vm/models.py
@@ -55,7 +55,9 @@ class VMDiskImageProduct(models.Model):
 
     """
 
-    uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
+    uuid = models.UUIDField(primary_key=True,
+                            default=uuid.uuid4,
+                            editable=False)
     owner = models.ForeignKey(get_user_model(),
                               on_delete=models.CASCADE,
                               editable=False)
@@ -64,7 +66,10 @@ class VMDiskImageProduct(models.Model):
     is_os_image = models.BooleanField(default=False)
     is_public = models.BooleanField(default=False)
 
-    size_in_gb = models.FloatField()
+    size_in_gb = models.FloatField(null=True,
+                                   blank=True)
+    import_url = models.URLField(null=True,
+                                 blank=True)
 
 
     storage_class = models.CharField(max_length=32,
diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py
index a64fdd0..f8618ee 100644
--- a/uncloud/uncloud_vm/serializers.py
+++ b/uncloud/uncloud_vm/serializers.py
@@ -3,6 +3,13 @@ from django.contrib.auth import get_user_model
 from rest_framework import serializers
 from .models import VMHost, VMProduct, VMSnapshotProduct, VMDiskProduct, VMDiskImageProduct
 
+GB_SSD_PER_DAY=0.012
+GB_HDD_PER_DAY=0.0006
+
+GB_SSD_PER_DAY=0.012
+GB_HDD_PER_DAY=0.0006
+
+
 class VMHostSerializer(serializers.ModelSerializer):
     class Meta:
         model = VMHost
@@ -32,11 +39,9 @@ class VMSnapshotProductSerializer(serializers.ModelSerializer):
 
     # verify that vm.owner == user.request
     def validate_vm(self, value):
-
         if not value.owner == self.context['request'].user:
             raise serializers.ValidationError("VM {} not found for owner {}.".format(value,
                                                                                      self.context['request'].user))
-
         disks = VMDiskProduct.objects.filter(vm=value)
 
         if len(disks) == 0:
diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py
index b9d80f9..851041e 100644
--- a/uncloud/uncloud_vm/views.py
+++ b/uncloud/uncloud_vm/views.py
@@ -26,7 +26,14 @@ class VMDiskImageProductMineViewSet(viewsets.ModelViewSet):
     def get_queryset(self):
         return VMDiskImageProduct.objects.filter(owner=self.request.user)
 
-class VMDiskImageProductPublicViewSet(viewsets.ModelViewSet):
+    def create(self, request):
+        serializer = VMProductSerializer(data=request.data, context={'request': request})
+        serializer.is_valid(raise_exception=True)
+        serializer.save(owner=request.user)
+        return Response(serializer.data)
+
+
+class VMDiskImageProductPublicViewSet(viewsets.ReadOnlyModelViewSet):
     permission_classes = [permissions.IsAuthenticated]
     serializer_class = VMDiskImageProductSerializer
 
@@ -55,7 +62,6 @@ class VMProductViewSet(viewsets.ModelViewSet):
         order.save()
 
         serializer.save(owner=request.user, order=order)
-
         return Response(serializer.data)
 
 

From 5c33bc5c02411778e354b322cdcd258b8b33ffc8 Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Sat, 29 Feb 2020 17:57:57 +0100
Subject: [PATCH 22/23] support creating disks

Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
---
 uncloud/uncloud/urls.py           | 15 +++++++++++-
 uncloud/uncloud_vm/models.py      | 32 +++++++++++++------------
 uncloud/uncloud_vm/serializers.py |  2 ++
 uncloud/uncloud_vm/views.py       | 40 +++++++++++++++++++++++++++++--
 4 files changed, 71 insertions(+), 18 deletions(-)

diff --git a/uncloud/uncloud/urls.py b/uncloud/uncloud/urls.py
index 40b3b20..02862a1 100644
--- a/uncloud/uncloud/urls.py
+++ b/uncloud/uncloud/urls.py
@@ -26,14 +26,27 @@ router = routers.DefaultRouter()
 
 # user / regular urls
 router.register(r'vm/snapshot', vmviews.VMSnapshotProductViewSet, basename='vmsnapshotproduct')
+router.register(r'vm/disk', vmviews.VMDiskProductViewSet, basename='vmdiskproduct')
 router.register(r'vm/image/mine', vmviews.VMDiskImageProductMineViewSet, basename='vmdiskimagemineproduct')
 router.register(r'vm/image/public', vmviews.VMDiskImageProductPublicViewSet, basename='vmdiskimagepublicproduct')
 
+# images the provider provides :-)
+# router.register(r'vm/image/official', vmviews.VMDiskImageProductPublicViewSet, basename='vmdiskimagepublicproduct')
+
+
 
-#router.register(r'vm/disk', vmviews.VMDiskProductViewSet, basename='vmdiskproduct')
 
 router.register(r'vm/vm', vmviews.VMProductViewSet, basename='vmproduct')
 
+# TBD
+#router.register(r'vm/disk', vmviews.VMDiskProductViewSet, basename='vmdiskproduct')
+
+# creates VM from os image
+#router.register(r'vm/ipv6onlyvm', vmviews.VMProductViewSet, basename='vmproduct')
+# ... AND adds IPv4 mapping
+#router.register(r'vm/dualstackvm', vmviews.VMProductViewSet, basename='vmproduct')
+
+# allow vm creation from own images
 
 
 # Pay
diff --git a/uncloud/uncloud_vm/models.py b/uncloud/uncloud_vm/models.py
index f2cbf13..7aac05b 100644
--- a/uncloud/uncloud_vm/models.py
+++ b/uncloud/uncloud_vm/models.py
@@ -4,6 +4,16 @@ import uuid
 
 from uncloud_pay.models import Product
 
+STATUS_CHOICES = (
+    ('pending', 'Pending'),   # Initial state
+    ('creating', 'Creating'), # Creating VM/image/etc.
+    ('active', 'Active'),     # Is usable / active
+    ('disabled', 'Disabled'), # Is usable, but cannot be used for new things
+    ('unusable', 'Unusable'), # Has some kind of error
+    ('deleted', 'Deleted'),   # Does not exist anymore, only DB entry as a log
+)
+
+STATUS_DEFAULT='pending'
 
 class VMHost(models.Model):
     uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
@@ -22,13 +32,8 @@ class VMHost(models.Model):
 
 
     status = models.CharField(max_length=32,
-                              choices = (
-                                  ('pending', 'Pending'),
-                                  ('active', 'Active'),
-                                  ('unusable', 'Unusable'),
-                                  ('deleted', 'Deleted'),
-                              ),
-                              default='pending'
+                              choices=STATUS_CHOICES,
+                              default=STATUS_DEFAULT
     )
 
 
@@ -80,13 +85,10 @@ class VMDiskImageProduct(models.Model):
                               default='ssd'
     )
 
-    # source = models.CharField(max_length=32,
-    #                           choices = (
-    #                               ('url', 'HDD'),
-    #                               ('ssd', 'SSD'),
-    #                           ),
-    #                           default='ssd'
-    # )
+    status = models.CharField(max_length=32,
+                              choices=STATUS_CHOICES,
+                              default=STATUS_DEFAULT
+    )
 
 class VMDiskProduct(models.Model):
     """
@@ -105,7 +107,7 @@ class VMDiskProduct(models.Model):
     vm   = models.ForeignKey(VMProduct, on_delete=models.CASCADE)
     image = models.ForeignKey(VMDiskImageProduct, on_delete=models.CASCADE)
 
-    size_in_gb = models.FloatField()
+    size_in_gb = models.FloatField(blank=True)
 
 
 class VMNetworkCard(models.Model):
diff --git a/uncloud/uncloud_vm/serializers.py b/uncloud/uncloud_vm/serializers.py
index f8618ee..07d6c51 100644
--- a/uncloud/uncloud_vm/serializers.py
+++ b/uncloud/uncloud_vm/serializers.py
@@ -22,6 +22,8 @@ class VMProductSerializer(serializers.ModelSerializer):
         fields = '__all__'
 
 class VMDiskProductSerializer(serializers.ModelSerializer):
+#    vm = VMProductSerializer()
+
     class Meta:
         model = VMDiskProduct
         fields = '__all__'
diff --git a/uncloud/uncloud_vm/views.py b/uncloud/uncloud_vm/views.py
index 851041e..62edaa0 100644
--- a/uncloud/uncloud_vm/views.py
+++ b/uncloud/uncloud_vm/views.py
@@ -5,11 +5,13 @@ from django.shortcuts import get_object_or_404
 
 from rest_framework import viewsets, permissions
 from rest_framework.response import Response
+from rest_framework.exceptions import ValidationError
+
 
 from .models import VMHost, VMProduct, VMSnapshotProduct, VMDiskProduct, VMDiskImageProduct
 from uncloud_pay.models import Order
 
-from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer, VMDiskImageProductSerializer
+from .serializers import VMHostSerializer, VMProductSerializer, VMSnapshotProductSerializer, VMDiskImageProductSerializer, VMDiskProductSerializer
 
 
 import datetime
@@ -27,8 +29,14 @@ class VMDiskImageProductMineViewSet(viewsets.ModelViewSet):
         return VMDiskImageProduct.objects.filter(owner=self.request.user)
 
     def create(self, request):
-        serializer = VMProductSerializer(data=request.data, context={'request': request})
+        serializer = VMDiskImageProductSerializer(data=request.data, context={'request': request})
         serializer.is_valid(raise_exception=True)
+
+        # did not specify size NOR import url?
+        if not serializer.validated_data['size_in_gb']:
+            if not serializer.validated_data['import_url']:
+                raise ValidationError(detail={ 'error_mesage': 'Specify either import_url or size_in_gb' })
+
         serializer.save(owner=request.user)
         return Response(serializer.data)
 
@@ -40,6 +48,34 @@ class VMDiskImageProductPublicViewSet(viewsets.ReadOnlyModelViewSet):
     def get_queryset(self):
         return VMDiskImageProduct.objects.filter(is_public=True)
 
+class VMDiskProductViewSet(viewsets.ModelViewSet):
+    """
+    Let a user modify their own VMDisks
+    """
+    permission_classes = [permissions.IsAuthenticated]
+    serializer_class = VMDiskProductSerializer
+
+    def get_queryset(self):
+        return VMDiskProduct.objects.filter(owner=self.request.user)
+
+    def create(self, request):
+        serializer = VMDiskProductSerializer(data=request.data, context={'request': request})
+        serializer.is_valid(raise_exception=True)
+
+        # get disk size from image, if not specified
+        if not 'size_in_gb' in serializer.validated_data:
+            size_in_gb = serializer.validated_data['image'].size_in_gb
+        else:
+            size_in_gb = serializer.validated_data['size_in_gb']
+
+        if size_in_gb < serializer.validated_data['image'].size_in_gb:
+            raise ValidationError(detail={ 'error_mesage': 'Size is smaller than original image' })
+
+
+        serializer.save(owner=request.user, size_in_gb=size_in_gb)
+        return Response(serializer.data)
+
+
 
 class VMProductViewSet(viewsets.ModelViewSet):
     permission_classes = [permissions.IsAuthenticated]

From 4115eed2a8398144ef1f1faba628e9a9d2c1edfd Mon Sep 17 00:00:00 2001
From: Nico Schottelius <nico@nico-notebook.schottelius.org>
Date: Sat, 29 Feb 2020 17:58:10 +0100
Subject: [PATCH 23/23] +migration

Signed-off-by: Nico Schottelius <nico@nico-notebook.schottelius.org>
---
 .../migrations/0008_auto_20200229_1611.py     | 23 +++++++++++++++++++
 1 file changed, 23 insertions(+)
 create mode 100644 uncloud/uncloud_vm/migrations/0008_auto_20200229_1611.py

diff --git a/uncloud/uncloud_vm/migrations/0008_auto_20200229_1611.py b/uncloud/uncloud_vm/migrations/0008_auto_20200229_1611.py
new file mode 100644
index 0000000..8a9be67
--- /dev/null
+++ b/uncloud/uncloud_vm/migrations/0008_auto_20200229_1611.py
@@ -0,0 +1,23 @@
+# Generated by Django 3.0.3 on 2020-02-29 16:11
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('uncloud_vm', '0007_auto_20200229_1559'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='vmdiskimageproduct',
+            name='status',
+            field=models.CharField(choices=[('pending', 'Pending'), ('creating', 'Creating'), ('active', 'Active'), ('disabled', 'Disabled'), ('unusable', 'Unusable'), ('deleted', 'Deleted')], default='pending', max_length=32),
+        ),
+        migrations.AlterField(
+            model_name='vmhost',
+            name='status',
+            field=models.CharField(choices=[('pending', 'Pending'), ('creating', 'Creating'), ('active', 'Active'), ('disabled', 'Disabled'), ('unusable', 'Unusable'), ('deleted', 'Deleted')], default='pending', max_length=32),
+        ),
+    ]