From 2ff8b9e4a59815ed40e937c1bbbada95c99adaf7 Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Fri, 5 May 2017 14:59:11 +0200
Subject: [PATCH 01/15] Add hosting bill view, model and urls

---
 hosting/admin.py                           |  4 +-
 hosting/migrations/0028_managevms.py       | 24 ++++++
 hosting/migrations/0029_managevm.py        | 24 ++++++
 hosting/migrations/0030_hostingbill.py     | 31 ++++++++
 hosting/models.py                          | 16 ++++
 hosting/templates/hosting/bill_detail.html | 93 ++++++++++++++++++++++
 hosting/templates/hosting/bills.html       | 60 ++++++++++++++
 hosting/urls.py                            |  5 +-
 hosting/views.py                           | 76 +++++++++++++++++-
 9 files changed, 330 insertions(+), 3 deletions(-)
 create mode 100644 hosting/migrations/0028_managevms.py
 create mode 100644 hosting/migrations/0029_managevm.py
 create mode 100644 hosting/migrations/0030_hostingbill.py
 create mode 100644 hosting/templates/hosting/bill_detail.html
 create mode 100644 hosting/templates/hosting/bills.html

diff --git a/hosting/admin.py b/hosting/admin.py
index b4f1ba24..ee4e7415 100644
--- a/hosting/admin.py
+++ b/hosting/admin.py
@@ -5,7 +5,8 @@ from django.core.urlresolvers import reverse
 from utils.mailer import BaseEmail
 
 from .forms import HostingOrderAdminForm
-from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, ManageVM
+from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, \
+                    ManageVM, HostingBill
 from .opennebula_functions import HostingManageVMAdmin
 
 
@@ -98,3 +99,4 @@ admin.site.register(HostingOrder, HostingOrderAdmin)
 admin.site.register(VirtualMachineType)
 admin.site.register(VirtualMachinePlan, VirtualMachinePlanAdmin)
 admin.site.register(ManageVM, HostingManageVMAdmin)
+admin.site.register(HostingBill)
diff --git a/hosting/migrations/0028_managevms.py b/hosting/migrations/0028_managevms.py
new file mode 100644
index 00000000..b71480f2
--- /dev/null
+++ b/hosting/migrations/0028_managevms.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2017-04-24 04:24
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('hosting', '0027_auto_20160711_0210'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ManageVMs',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+            ],
+            options={
+                'managed': False,
+            },
+        ),
+    ]
diff --git a/hosting/migrations/0029_managevm.py b/hosting/migrations/0029_managevm.py
new file mode 100644
index 00000000..946e4264
--- /dev/null
+++ b/hosting/migrations/0029_managevm.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2017-04-24 04:25
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('hosting', '0028_managevms'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ManageVM',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+            ],
+            options={
+                'managed': False,
+            },
+        ),
+    ]
diff --git a/hosting/migrations/0030_hostingbill.py b/hosting/migrations/0030_hostingbill.py
new file mode 100644
index 00000000..edb01aed
--- /dev/null
+++ b/hosting/migrations/0030_hostingbill.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2017-05-05 11:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import utils.mixins
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('utils', '0005_auto_20170322_1443'),
+        ('membership', '0006_auto_20160526_0445'),
+        ('hosting', '0029_managevm'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='HostingBill',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('billing_address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='utils.BillingAddress')),
+                ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='membership.StripeCustomer')),
+            ],
+            options={
+                'permissions': (('view_hostingbill', 'View Hosting Bill'),),
+            },
+            bases=(utils.mixins.AssignPermissionsMixin, models.Model),
+        ),
+    ]
diff --git a/hosting/models.py b/hosting/models.py
index b24dc855..a27fb487 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -1,5 +1,6 @@
 import os
 
+import oca
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 from django.utils.functional import cached_property
@@ -233,3 +234,18 @@ class ManageVM(models.Model):
 
     class Meta:
         managed = False
+
+class HostingBill(AssignPermissionsMixin, models.Model):
+    customer = models.ForeignKey(StripeCustomer)
+    billing_address = models.ForeignKey(BillingAddress)
+
+    permissions = ('view_hostingbill',)
+
+    class Meta:
+        permissions = (
+            ('view_hostingbill', 'View Hosting Bill'),
+        )
+
+    def __str__(self):
+        return "%s" % (self.customer.user.email)
+
diff --git a/hosting/templates/hosting/bill_detail.html b/hosting/templates/hosting/bill_detail.html
new file mode 100644
index 00000000..1904bf82
--- /dev/null
+++ b/hosting/templates/hosting/bill_detail.html
@@ -0,0 +1,93 @@
+{% extends "hosting/base_short.html" %}
+{% load staticfiles bootstrap3 %}
+{% load i18n %}
+{% block content %}
+
+<h1> {{ bill }} </h1>
+<div class="container">
+	{# Adress bar  #}
+    <div class="row">
+            <div class="invoice-title">
+                <h2>{% trans "Invoice"%}</h2><h3 class="pull-right">{% trans "Order #"%} {{bill.id}}</h3>
+            </div>
+	</div>
+    <hr>
+	<div class="row">
+                <div class="col-sm-6">
+                    <address>
+                        {{bill.customer.user.name}}<br>
+                        {{bill.billing_address.street_address}},{{bill.billing_address.postal_code}}<br>
+                        {{bill.billing_address.city}}, {{bill.billing_address.country}}.
+                    </address>
+                </div>
+                <div class="col-sm-6 text-right">
+                    <address>
+							{% trans "ungleich GmbH" %}<br>
+							{% trans "buchhaltung@ungleich.ch" %}<br>
+							{% trans "Hauptstrasse 14"%}<br>
+							{% trans "CH-8775 Luchsingen"%}<br>
+							{% trans "Mwst-Nummer: CHE-109.549.333 MWST"%}<br>
+
+                    </address>
+				</div>
+	</div>
+	<hr>
+	<table class="table table-bordered"> 
+	    {# Bill header #}
+        <thead>
+            <tr>
+                <th>Name</th>
+                <th>Cores</th>
+                <th>Memory</th>
+                <th>Disk Size</th>
+                <th>Price</th>
+            </tr>
+        </thead>
+        <tbody>
+	        {# Bill items#}
+            {% for vm in vms %}
+                <tr>
+                    <td>{{ vm.name }}</td>
+                    <td>{{ vm.cores }}</td>
+                    <td>{{ vm.memory }}</td>
+                    <td>{{ vm.disk_size }}</td>
+                    <td>{{ vm.price }}</td>
+
+                </tr>
+            {% endfor %}
+	        {# Bill total#}
+            <tr>
+                <td> {% trans "Total:" %} </td>
+                <td>  </td>
+                <td>  </td>
+                <td> {% trans "Brutto" %} </td>
+                <td> {% trans "Netto" %} </td>
+            </tr>
+        </tbody>
+    </table>
+	<hr>
+    {# Bill Footer #}
+	<div class="row">
+			{% trans "Alles Preise in CHF mit 8% Mehrwertsteuer." %}
+			{% trans "Betrag zahlbar innerhalb von 30 Tagen ab Rechnungseingang." %}
+			{% trans "Kontoverbindung:" %}
+			<div class="row">
+				<div class="col-sm-6">
+					{% trans "IBAN:" %}
+				</div>
+				<div class="col-sm-6">
+					{% trans "BIC:" %}
+				</div>
+			</div>
+			<div class="row">
+				<div class="col-sm-6">
+					{% trans "CH02 ............" %}
+				</div>
+				<div class="col-sm-6">
+					{% trans "POFICHBEXXX" %}
+				</div>
+			</div>
+	</div>
+</div>
+{% endblock %}
+			
diff --git a/hosting/templates/hosting/bills.html b/hosting/templates/hosting/bills.html
new file mode 100644
index 00000000..cc1883fb
--- /dev/null
+++ b/hosting/templates/hosting/bills.html
@@ -0,0 +1,60 @@
+{% extends "hosting/base_short.html" %}
+{% load staticfiles bootstrap3 %}
+{% load i18n %}
+
+{% block content %}
+
+    <div>
+        <div class="container orders-container">
+            <div class="row">
+                <div class="col-md-8 col-md-offset-2">
+                    <table class="table borderless table-hover">
+                        <h3>{% trans "Customers"%}</h3>
+                        <br/>
+                        <thead>
+                        <tr>
+                            <th>{% trans "Name"%}</th>
+                            <th>{% trans "Email"%}</th>
+                            <th></th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        {% for user in users %}
+                            <tr>
+                                <td>{{ user.name}}</td>
+                                <td>{{ user.email}} CHF</td>
+                                <td>
+                                    <button type="button" class="btn btn-default"><a
+                                            href="{% url 'hosting:bills' user.id %}">{% trans "View Bill"%}</a>
+                                    </button>
+                                </td>
+                            </tr>
+                        {% endfor %}
+
+                        </tbody>
+                    </table>
+
+                    {% if is_paginated %}
+                        <div class="pagination">
+			            <span class="page-links">
+			                {% if page_obj.has_previous %}
+                                <a href="{{ request.path }}?page={{ page_obj.previous_page_number }}">{% trans "previous"%}</a>
+                            {% endif %}
+                            <span class="page-current">
+			                    Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
+			                </span>
+                            {% if page_obj.has_next %}
+                                <a href="{{ request.path }}?page={{ page_obj.next_page_number }}">{% trans "next"%}</a>
+                            {% endif %}
+			            </span>
+                        </div>
+                    {% endif %}
+
+                </div>
+
+            </div>
+        </div>
+
+    </div>
+
+{% endblock %}
diff --git a/hosting/urls.py b/hosting/urls.py
index 5ceeba97..1df6be9e 100644
--- a/hosting/urls.py
+++ b/hosting/urls.py
@@ -4,7 +4,8 @@ from .views import DjangoHostingView, RailsHostingView, PaymentVMView,\
     NodeJSHostingView, LoginView, SignupView, IndexView, \
     OrdersHostingListView, OrdersHostingDetailView, VirtualMachinesPlanListView,\
     VirtualMachineView, GenerateVMSSHKeysView, OrdersHostingDeleteView, NotificationsView, \
-    MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, HostingPricingView
+    MarkAsReadNotificationView, PasswordResetView, PasswordResetConfirmView, \
+    HostingPricingView, HostingBillListView, HostingBillDetailView
 
 urlpatterns = [
     url(r'index/?$', IndexView.as_view(), name='index'),
@@ -15,6 +16,8 @@ urlpatterns = [
     url(r'payment/?$', PaymentVMView.as_view(), name='payment'),
     url(r'orders/?$', OrdersHostingListView.as_view(), name='orders'),
     url(r'orders/(?P<pk>\d+)/?$', OrdersHostingDetailView.as_view(), name='orders'),
+    url(r'bills/?$', HostingBillListView.as_view(), name='bills'),
+    url(r'bills/(?P<pk>\d+)/?$', HostingBillDetailView.as_view(), name='bills'),
     url(r'cancel_order/(?P<pk>\d+)/?$', OrdersHostingDeleteView.as_view(), name='delete_order'),
     url(r'my-virtual-machines/?$', VirtualMachinesPlanListView.as_view(), name='virtual_machines'),
     url(r'my-virtual-machines/(?P<pk>\d+)/?$', VirtualMachineView.as_view(),
diff --git a/hosting/views.py b/hosting/views.py
index 6b21c91b..c9634200 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1,3 +1,4 @@
+import oca
 
 from django.shortcuts import render
 from django.core.urlresolvers import reverse_lazy, reverse
@@ -19,7 +20,7 @@ from utils.stripe_utils import StripeUtils
 from utils.forms import BillingAddressForm, PasswordResetRequestForm
 from utils.views import PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin
 from utils.mailer import BaseEmail
-from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder
+from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, HostingBill
 from .forms import HostingUserSignupForm, HostingUserLoginForm
 from .mixins import ProcessVMSelectionMixin
 
@@ -328,6 +329,10 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin, Detai
     permission_required = ['view_hostingorder']
     model = HostingOrder
 
+    def get_context_data(self, **kwargs):
+        context = super(DetailView, self).get_context_data(**kwargs)
+        print(context)
+        return context
 
 class OrdersHostingListView(LoginRequiredMixin, ListView):
     template_name = "hosting/orders.html"
@@ -396,3 +401,72 @@ class VirtualMachineView(PermissionRequiredMixin, LoginRequiredMixin, UpdateView
         email.send()
 
         return HttpResponseRedirect(self.get_success_url())
+
+class HostingBillListView(LoginRequiredMixin, ListView):
+    template_name = "hosting/bills.html"
+    login_url = reverse_lazy('hosting:login')
+    context_object_name = "users"
+    model = StripeCustomer
+    paginate_by = 10
+    ordering = '-id'
+    #TODO show only clients i.e. get_query_set
+
+class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailView):
+    template_name = "hosting/bill_detail.html"
+    login_url = reverse_lazy('hosting:login')
+    permission_required = ['view_hostingview']
+    context_object_name = "bill"
+    model = HostingBill
+
+    def get_object(self, queryset=None):
+        #Get HostingBill for primary key (Select from customer users)
+        pk = self.kwargs['pk']
+        return HostingBill.objects.filter(customer__id=pk).first()
+
+    def get_context_data(self, **kwargs):
+        # Get User
+        user_email = self.object.customer.user.email
+        # Get context
+        context = super(DetailView, self).get_context_data(**kwargs)
+        # Add VMs to context
+        context['vms'] = []
+
+        # Connect to open nebula server
+        client = oca.Client("{0}:{1}".format(settings.OPENNEBULA_USERNAME,
+                                             settings.OPENNEBULA_PASSWORD),
+                            "{protocol}://{domain}:{port}{endpoint}".format(
+                                protocol=settings.OPENNEBULA_PROTOCOL,
+                                domain=settings.OPENNEBULA_DOMAIN,
+                                port=settings.OPENNEBULA_PORT,
+                                endpoint=settings.OPENNEBULA_ENDPOINT
+                            ))
+        # Get open nebula user id for given email 
+        user_pool = oca.UserPool(client)
+        user_pool.info()
+        user_id = user_pool.get_by_name('alain').id
+
+        # Get vm_pool for given user_id
+        vm_pool = oca.VirtualMachinePool(client)
+        vm_pool.info(filter=user_id)
+        # Add vm in vm_pool to context
+        for vm in vm_pool:
+            #TODO: Replace with vm plan 
+            name = vm.name
+            cores = int(vm.template.vcpu)
+            memory = int(vm.template.memory) / 1024
+            # Check if vm has more than one disk
+            if 'DISK' in vm.template.multiple:
+                disk_size = 0
+                for disk in vm.template.disks:
+                    disk_size += int(disk.size) / 1024
+            else:
+                disk_size = int(vm.template.disk.size) / 1024
+            vm = {}
+            vm['name'] = name
+            vm['price'] = 0.6 * disk_size + 2 * memory + 5 * cores
+            vm['disk_size'] = disk_size
+            vm['cores'] = cores 
+            vm['memory'] = memory
+            context['vms'].append(vm)
+
+        return context

From 673e8a0c79df1b5d601c75913950d3c77e0a3e1e Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Sat, 6 May 2017 14:44:08 +0200
Subject: [PATCH 02/15] Add total_price to HostingBill model

---
 .../0031_hostingbill_total_price.py           | 20 ++++
 hosting/models.py                             |  1 +
 hosting/templates/hosting/bill_detail.html    | 91 +++++++++----------
 hosting/views.py                              | 10 +-
 4 files changed, 73 insertions(+), 49 deletions(-)
 create mode 100644 hosting/migrations/0031_hostingbill_total_price.py

diff --git a/hosting/migrations/0031_hostingbill_total_price.py b/hosting/migrations/0031_hostingbill_total_price.py
new file mode 100644
index 00000000..0a15c1f9
--- /dev/null
+++ b/hosting/migrations/0031_hostingbill_total_price.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2017-05-06 12:30
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('hosting', '0030_hostingbill'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='hostingbill',
+            name='total_price',
+            field=models.FloatField(default=0.0),
+        ),
+    ]
diff --git a/hosting/models.py b/hosting/models.py
index a27fb487..8c6a2c0d 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -238,6 +238,7 @@ class ManageVM(models.Model):
 class HostingBill(AssignPermissionsMixin, models.Model):
     customer = models.ForeignKey(StripeCustomer)
     billing_address = models.ForeignKey(BillingAddress)
+    total_price = models.FloatField(default=0.0)
 
     permissions = ('view_hostingbill',)
 
diff --git a/hosting/templates/hosting/bill_detail.html b/hosting/templates/hosting/bill_detail.html
index 1904bf82..b58c21f7 100644
--- a/hosting/templates/hosting/bill_detail.html
+++ b/hosting/templates/hosting/bill_detail.html
@@ -7,29 +7,29 @@
 <div class="container">
 	{# Adress bar  #}
     <div class="row">
-            <div class="invoice-title">
-                <h2>{% trans "Invoice"%}</h2><h3 class="pull-right">{% trans "Order #"%} {{bill.id}}</h3>
-            </div>
+        <div class="invoice-title">
+            <h2>{% trans "Invoice"%}</h2><h3 class="pull-right">{% trans "Order #"%} {{bill.id}}</h3>
+        </div>
 	</div>
     <hr>
 	<div class="row">
-                <div class="col-sm-6">
-                    <address>
-                        {{bill.customer.user.name}}<br>
-                        {{bill.billing_address.street_address}},{{bill.billing_address.postal_code}}<br>
-                        {{bill.billing_address.city}}, {{bill.billing_address.country}}.
-                    </address>
-                </div>
-                <div class="col-sm-6 text-right">
-                    <address>
-							{% trans "ungleich GmbH" %}<br>
-							{% trans "buchhaltung@ungleich.ch" %}<br>
-							{% trans "Hauptstrasse 14"%}<br>
-							{% trans "CH-8775 Luchsingen"%}<br>
-							{% trans "Mwst-Nummer: CHE-109.549.333 MWST"%}<br>
+        <div class="col-sm-6">
+            <address>
+                {{bill.customer.user.name}}<br>
+                {{bill.billing_address.street_address}},{{bill.billing_address.postal_code}}<br>
+                {{bill.billing_address.city}}, {{bill.billing_address.country}}.
+            </address>
+        </div>
+        <div class="col-sm-6 text-right">
+            <address>
+	    			{% trans "ungleich GmbH" %}<br>
+	    			{% trans "buchhaltung@ungleich.ch" %}<br>
+	    			{% trans "Hauptstrasse 14"%}<br>
+	    			{% trans "CH-8775 Luchsingen"%}<br>
+	    			{% trans "Mwst-Nummer: CHE-109.549.333 MWST"%}<br>
 
-                    </address>
-				</div>
+            </address>
+	    </div>
 	</div>
 	<hr>
 	<table class="table table-bordered"> 
@@ -48,45 +48,42 @@
             {% for vm in vms %}
                 <tr>
                     <td>{{ vm.name }}</td>
-                    <td>{{ vm.cores }}</td>
-                    <td>{{ vm.memory }}</td>
-                    <td>{{ vm.disk_size }}</td>
-                    <td>{{ vm.price }}</td>
+                    <td><span class="pull-right">{{ vm.cores }}</span></td>
+                    <td><span class="pull-right">{{ vm.memory|floatformat }} GiB </span></td>
+                    <td><span class="pull-right">{{ vm.disk_size|floatformat }} GiB </span></td>
+                    <td><span class="pull-right">{{ vm.price|floatformat }} CHF</span></td>
 
                 </tr>
             {% endfor %}
 	        {# Bill total#}
             <tr>
-                <td> {% trans "Total:" %} </td>
-                <td>  </td>
-                <td>  </td>
-                <td> {% trans "Brutto" %} </td>
-                <td> {% trans "Netto" %} </td>
+                <td colspan=4> {% trans "Total:" %} </td>
+                <td> <span class="pull-right">{{ bill.total_price}} CHF </span></td> 
             </tr>
         </tbody>
     </table>
 	<hr>
     {# Bill Footer #}
 	<div class="row">
-			{% trans "Alles Preise in CHF mit 8% Mehrwertsteuer." %}
-			{% trans "Betrag zahlbar innerhalb von 30 Tagen ab Rechnungseingang." %}
-			{% trans "Kontoverbindung:" %}
-			<div class="row">
-				<div class="col-sm-6">
-					{% trans "IBAN:" %}
-				</div>
-				<div class="col-sm-6">
-					{% trans "BIC:" %}
-				</div>
-			</div>
-			<div class="row">
-				<div class="col-sm-6">
-					{% trans "CH02 ............" %}
-				</div>
-				<div class="col-sm-6">
-					{% trans "POFICHBEXXX" %}
-				</div>
-			</div>
+	    {% trans "Alles Preise in CHF mit 8% Mehrwertsteuer." %}
+	    {% trans "Betrag zahlbar innerhalb von 30 Tagen ab Rechnungseingang." %}
+	    {% trans "Kontoverbindung:" %}
+	    <div class="row">
+	    	<div class="col-sm-6">
+	    		{% trans "IBAN:" %}
+	    	</div>
+	    	<div class="col-sm-6">
+	    		{% trans "BIC:" %}
+	    	</div>
+	    </div>
+	    <div class="row">
+	    	<div class="col-sm-6">
+	    		{% trans "CH02 ............" %}
+	    	</div>
+	    	<div class="col-sm-6">
+	    		{% trans "POFICHBEXXX" %}
+	    	</div>
+	    </div>
 	</div>
 </div>
 {% endblock %}
diff --git a/hosting/views.py b/hosting/views.py
index c9634200..b552063a 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -448,9 +448,10 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailV
         # Get vm_pool for given user_id
         vm_pool = oca.VirtualMachinePool(client)
         vm_pool.info(filter=user_id)
+        # Reset total price
+        context['bill'].total_price = 0 
         # Add vm in vm_pool to context
         for vm in vm_pool:
-            #TODO: Replace with vm plan 
             name = vm.name
             cores = int(vm.template.vcpu)
             memory = int(vm.template.memory) / 1024
@@ -461,12 +462,17 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailV
                     disk_size += int(disk.size) / 1024
             else:
                 disk_size = int(vm.template.disk.size) / 1024
+
+            #TODO: Replace with vm plan 
+            price = 0.6 * disk_size + 2 * memory + 5 * cores
             vm = {}
             vm['name'] = name
-            vm['price'] = 0.6 * disk_size + 2 * memory + 5 * cores
+            vm['price'] = price
             vm['disk_size'] = disk_size
             vm['cores'] = cores 
             vm['memory'] = memory
             context['vms'].append(vm)
+            context['bill'].total_price += price
 
+        context['bill'].save()
         return context

From 6f252def5d9aa7bda7ea9bb99036a69cb3b1114d Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Sat, 6 May 2017 15:16:10 +0200
Subject: [PATCH 03/15] Return error if HostingBill object does not exist

---
 hosting/templates/hosting/bill_error.html | 14 ++++++++++++++
 hosting/views.py                          |  8 ++++++--
 2 files changed, 20 insertions(+), 2 deletions(-)
 create mode 100644 hosting/templates/hosting/bill_error.html

diff --git a/hosting/templates/hosting/bill_error.html b/hosting/templates/hosting/bill_error.html
new file mode 100644
index 00000000..5374ecb5
--- /dev/null
+++ b/hosting/templates/hosting/bill_error.html
@@ -0,0 +1,14 @@
+{% extends "hosting/base_short.html" %}
+{% load staticfiles bootstrap3 %}
+{% load i18n %}
+{% block content %}
+
+<div class="container">
+    <div class="container orders-container">
+    <h1>Error</h1>
+    <p> Could not get HostingBill object for client. </p>
+    <p> Please create a HostingBill object via the admin page </p>
+    </div>
+</div>
+{% endblock %}
+			
diff --git a/hosting/views.py b/hosting/views.py
index b552063a..b5a18597 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -424,10 +424,14 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailV
         return HostingBill.objects.filter(customer__id=pk).first()
 
     def get_context_data(self, **kwargs):
-        # Get User
-        user_email = self.object.customer.user.email
         # Get context
         context = super(DetailView, self).get_context_data(**kwargs)
+        # Get User
+        try:
+            user_email = self.object.customer.user.email
+        except AttributeError:
+            self.template_name = 'hosting/bill_error.html'
+            return context
         # Add VMs to context
         context['vms'] = []
 

From bd362cb6191fd2a2449d42b58823b547984669b1 Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Sat, 6 May 2017 15:28:18 +0200
Subject: [PATCH 04/15] Change to user_email add TODOs

---
 hosting/views.py | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/hosting/views.py b/hosting/views.py
index b5a18597..923a6f29 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -1,4 +1,5 @@
 import oca
+import socket
 
 from django.shortcuts import render
 from django.core.urlresolvers import reverse_lazy, reverse
@@ -7,6 +8,7 @@ from django.views.generic import View, CreateView, FormView, ListView, DetailVie
     DeleteView, TemplateView, UpdateView
 from django.http import HttpResponseRedirect
 from django.contrib.auth import authenticate, login
+from django.contrib import messages
 from django.conf import settings
 
 from guardian.mixins import PermissionRequiredMixin
@@ -24,6 +26,9 @@ from .models import VirtualMachineType, VirtualMachinePlan, HostingOrder, Hostin
 from .forms import HostingUserSignupForm, HostingUserLoginForm
 from .mixins import ProcessVMSelectionMixin
 
+from oca.exceptions import OpenNebulaException
+from oca.pool import WrongNameError
+
 
 class DjangoHostingView(ProcessVMSelectionMixin, View):
     template_name = "hosting/django.html"
@@ -436,6 +441,7 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailV
         context['vms'] = []
 
         # Connect to open nebula server
+        # TODO: handle potential connection error
         client = oca.Client("{0}:{1}".format(settings.OPENNEBULA_USERNAME,
                                              settings.OPENNEBULA_PASSWORD),
                             "{protocol}://{domain}:{port}{endpoint}".format(
@@ -447,11 +453,13 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailV
         # Get open nebula user id for given email 
         user_pool = oca.UserPool(client)
         user_pool.info()
-        user_id = user_pool.get_by_name('alain').id
+        # TODO: handle potential name error
+        user_id = user_pool.get_by_name(user_email).id
 
         # Get vm_pool for given user_id
         vm_pool = oca.VirtualMachinePool(client)
         vm_pool.info(filter=user_id)
+
         # Reset total price
         context['bill'].total_price = 0 
         # Add vm in vm_pool to context

From 4ab8963149f851687170ae55e26d75e7be221fd8 Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Sun, 7 May 2017 06:43:28 +0200
Subject: [PATCH 05/15] Follow fat models small views

Based on the recommondation in 'Two scoops of Django' I moved the code
for accessing the customers vm from the view to the model.
---
 hosting/models.py | 55 ++++++++++++++++++++++++++++++++++++++++
 hosting/views.py  | 64 ++---------------------------------------------
 2 files changed, 57 insertions(+), 62 deletions(-)

diff --git a/hosting/models.py b/hosting/models.py
index 4ca46844..e48f2f4b 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -267,3 +267,58 @@ class HostingBill(AssignPermissionsMixin, models.Model):
     def __str__(self):
         return "%s" % (self.customer.user.email)
 
+    def get_vms(self):
+        # Get User
+        user_email = self.customer.user.email
+
+        # Connect to open nebula server
+        # TODO: handle potential connection error
+        client = oca.Client("{0}:{1}".format(settings.OPENNEBULA_USERNAME,
+                                             settings.OPENNEBULA_PASSWORD),
+                            "{protocol}://{domain}:{port}{endpoint}".format(
+                                protocol=settings.OPENNEBULA_PROTOCOL,
+                                domain=settings.OPENNEBULA_DOMAIN,
+                                port=settings.OPENNEBULA_PORT,
+                                endpoint=settings.OPENNEBULA_ENDPOINT
+                            ))
+        # Get open nebula user id for given email
+        user_pool = oca.UserPool(client)
+        user_pool.info()
+        # TODO: handle potential name error
+        user_id = user_pool.get_by_name(user_email).id
+
+        # Get vm_pool for given user_id
+        vm_pool = oca.VirtualMachinePool(client)
+        vm_pool.info(filter=user_id)
+
+        # Reset total price
+        self.total_price = 0
+	vms = []
+        # Add vm in vm_pool to context
+        for vm in vm_pool:
+            name = vm.name
+            cores = int(vm.template.vcpu)
+            memory = int(vm.template.memory) / 1024
+            # Check if vm has more than one disk
+            if 'DISK' in vm.template.multiple:
+                disk_size = 0
+                for disk in vm.template.disks:
+                    disk_size += int(disk.size) / 1024
+            else:
+                disk_size = int(vm.template.disk.size) / 1024
+
+            #TODO: Replace with vm plan
+            price = 0.6 * disk_size + 2 * memory + 5 * cores
+            vm = {}
+            vm['name'] = name
+            vm['price'] = price
+            vm['disk_size'] = disk_size
+            vm['cores'] = cores
+            vm['memory'] = memory
+            vms.append(vm)
+            self.total_price += price
+
+        self.save()
+	return vms
+
+
diff --git a/hosting/views.py b/hosting/views.py
index 07bda4d2..91b92667 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -386,11 +386,6 @@ class OrdersHostingDetailView(PermissionRequiredMixin, LoginRequiredMixin, Detai
     permission_required = ['view_hostingorder']
     model = HostingOrder
 
-    def get_context_data(self, **kwargs):
-        context = super(DetailView, self).get_context_data(**kwargs)
-        print(context)
-        return context
-
 class OrdersHostingListView(LoginRequiredMixin, ListView):
     template_name = "hosting/orders.html"
     login_url = reverse_lazy('hosting:login')
@@ -520,60 +515,5 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailV
     def get_context_data(self, **kwargs):
         # Get context
         context = super(DetailView, self).get_context_data(**kwargs)
-        # Get User
-        try:
-            user_email = self.object.customer.user.email
-        except AttributeError:
-            self.template_name = 'hosting/bill_error.html'
-            return context
-        # Add VMs to context
-        context['vms'] = []
-
-        # Connect to open nebula server
-        # TODO: handle potential connection error
-        client = oca.Client("{0}:{1}".format(settings.OPENNEBULA_USERNAME,
-                                             settings.OPENNEBULA_PASSWORD),
-                            "{protocol}://{domain}:{port}{endpoint}".format(
-                                protocol=settings.OPENNEBULA_PROTOCOL,
-                                domain=settings.OPENNEBULA_DOMAIN,
-                                port=settings.OPENNEBULA_PORT,
-                                endpoint=settings.OPENNEBULA_ENDPOINT
-                            ))
-        # Get open nebula user id for given email 
-        user_pool = oca.UserPool(client)
-        user_pool.info()
-        # TODO: handle potential name error
-        user_id = user_pool.get_by_name(user_email).id
-
-        # Get vm_pool for given user_id
-        vm_pool = oca.VirtualMachinePool(client)
-        vm_pool.info(filter=user_id)
-
-        # Reset total price
-        context['bill'].total_price = 0 
-        # Add vm in vm_pool to context
-        for vm in vm_pool:
-            name = vm.name
-            cores = int(vm.template.vcpu)
-            memory = int(vm.template.memory) / 1024
-            # Check if vm has more than one disk
-            if 'DISK' in vm.template.multiple:
-                disk_size = 0
-                for disk in vm.template.disks:
-                    disk_size += int(disk.size) / 1024
-            else:
-                disk_size = int(vm.template.disk.size) / 1024
-
-            #TODO: Replace with vm plan 
-            price = 0.6 * disk_size + 2 * memory + 5 * cores
-            vm = {}
-            vm['name'] = name
-            vm['price'] = price
-            vm['disk_size'] = disk_size
-            vm['cores'] = cores 
-            vm['memory'] = memory
-            context['vms'].append(vm)
-            context['bill'].total_price += price
-
-        context['bill'].save()
-        return context
+        # Get vms
+        context['vms'] = self.get_object().get_vms()

From c2a76e6c39576169c6933bd293f8c20746d9dd92 Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Sun, 7 May 2017 16:09:41 +0200
Subject: [PATCH 06/15] Merge migrations and fix spacing

---
 hosting/migrations/0037_merge.py | 16 ++++++++++++++++
 hosting/models.py                |  5 +++--
 2 files changed, 19 insertions(+), 2 deletions(-)
 create mode 100644 hosting/migrations/0037_merge.py

diff --git a/hosting/migrations/0037_merge.py b/hosting/migrations/0037_merge.py
new file mode 100644
index 00000000..091a16c5
--- /dev/null
+++ b/hosting/migrations/0037_merge.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.4 on 2017-05-07 04:49
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('hosting', '0031_hostingbill_total_price'),
+        ('hosting', '0036_auto_20170506_2312'),
+    ]
+
+    operations = [
+    ]
diff --git a/hosting/models.py b/hosting/models.py
index e48f2f4b..63bf8654 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -4,6 +4,7 @@ import oca
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
 from django.utils.functional import cached_property
+from django.conf import settings
 
 from Crypto.PublicKey import RSA
 from stored_messages.settings import stored_messages_settings
@@ -293,7 +294,7 @@ class HostingBill(AssignPermissionsMixin, models.Model):
 
         # Reset total price
         self.total_price = 0
-	vms = []
+        vms = []
         # Add vm in vm_pool to context
         for vm in vm_pool:
             name = vm.name
@@ -319,6 +320,6 @@ class HostingBill(AssignPermissionsMixin, models.Model):
             self.total_price += price
 
         self.save()
-	return vms
+        return vms
 
 

From f871bb2aba11d1aab12543c74d71080cc0eb7d60 Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Mon, 8 May 2017 01:43:47 +0200
Subject: [PATCH 07/15] Show correct user name and email

---
 hosting/templates/hosting/bills.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/hosting/templates/hosting/bills.html b/hosting/templates/hosting/bills.html
index cc1883fb..1e789e12 100644
--- a/hosting/templates/hosting/bills.html
+++ b/hosting/templates/hosting/bills.html
@@ -21,8 +21,8 @@
                         <tbody>
                         {% for user in users %}
                             <tr>
-                                <td>{{ user.name}}</td>
-                                <td>{{ user.email}} CHF</td>
+                                <td>{{ user.user.name}}</td>
+                                <td>{{ user.user.email}}</td>
                                 <td>
                                     <button type="button" class="btn btn-default"><a
                                             href="{% url 'hosting:bills' user.id %}">{% trans "View Bill"%}</a>

From b4ec750728a4c9dfe90c3eeca3488f6bdf5bc1b5 Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Mon, 8 May 2017 01:56:02 +0200
Subject: [PATCH 08/15] Return error page if HostingBill is None

---
 hosting/views.py | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/hosting/views.py b/hosting/views.py
index 91b92667..4c66f40e 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -510,10 +510,18 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailV
     def get_object(self, queryset=None):
         #Get HostingBill for primary key (Select from customer users)
         pk = self.kwargs['pk']
-        return HostingBill.objects.filter(customer__id=pk).first()
+        object = HostingBill.objects.filter(customer__id=pk).first()
+        if object is None:
+            self.template_name = 'hosting/bill_error.html'
+        return object
 
     def get_context_data(self, **kwargs):
         # Get context
         context = super(DetailView, self).get_context_data(**kwargs)
         # Get vms
-        context['vms'] = self.get_object().get_vms()
+        try:
+            context['vms'] = self.get_object().get_vms()
+        except:
+            pass
+
+        return context

From a22c8cad51fc92b095663bf041024b8111fee211 Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Mon, 8 May 2017 02:46:11 +0200
Subject: [PATCH 09/15] Fix cosmetic detail

---
 hosting/templates/hosting/bill_detail.html | 155 +++++++++++----------
 1 file changed, 78 insertions(+), 77 deletions(-)

diff --git a/hosting/templates/hosting/bill_detail.html b/hosting/templates/hosting/bill_detail.html
index b58c21f7..449fa1bb 100644
--- a/hosting/templates/hosting/bill_detail.html
+++ b/hosting/templates/hosting/bill_detail.html
@@ -3,88 +3,89 @@
 {% load i18n %}
 {% block content %}
 
-<h1> {{ bill }} </h1>
 <div class="container">
-	{# Adress bar  #}
-    <div class="row">
-        <div class="invoice-title">
-            <h2>{% trans "Invoice"%}</h2><h3 class="pull-right">{% trans "Order #"%} {{bill.id}}</h3>
+    <div class="orders-container">
+        {# Adress bar  #}
+        <div class="row">
+            <div class="invoice-title">
+                <h2>{% trans "Invoice"%}</h2><h3 class="pull-right">{% trans "Order #"%} {{bill.id}}</h3>
+            </div>
         </div>
-	</div>
-    <hr>
-	<div class="row">
-        <div class="col-sm-6">
-            <address>
-                {{bill.customer.user.name}}<br>
-                {{bill.billing_address.street_address}},{{bill.billing_address.postal_code}}<br>
-                {{bill.billing_address.city}}, {{bill.billing_address.country}}.
-            </address>
-        </div>
-        <div class="col-sm-6 text-right">
-            <address>
-	    			{% trans "ungleich GmbH" %}<br>
-	    			{% trans "buchhaltung@ungleich.ch" %}<br>
-	    			{% trans "Hauptstrasse 14"%}<br>
-	    			{% trans "CH-8775 Luchsingen"%}<br>
-	    			{% trans "Mwst-Nummer: CHE-109.549.333 MWST"%}<br>
+        <hr>
+        <div class="row">
+            <div class="col-sm-6">
+                <address>
+                    {{bill.customer.user.name}}<br>
+                    {{bill.billing_address.street_address}},{{bill.billing_address.postal_code}}<br>
+                    {{bill.billing_address.city}}, {{bill.billing_address.country}}.
+                </address>
+            </div>
+            <div class="col-sm-6 text-right">
+                <address>
+                        {% trans "ungleich GmbH" %}<br>
+                        {% trans "buchhaltung@ungleich.ch" %}<br>
+                        {% trans "Hauptstrasse 14"%}<br>
+                        {% trans "CH-8775 Luchsingen"%}<br>
+                        {% trans "Mwst-Nummer: CHE-109.549.333 MWST"%}<br>
 
-            </address>
-	    </div>
-	</div>
-	<hr>
-	<table class="table table-bordered"> 
-	    {# Bill header #}
-        <thead>
-            <tr>
-                <th>Name</th>
-                <th>Cores</th>
-                <th>Memory</th>
-                <th>Disk Size</th>
-                <th>Price</th>
-            </tr>
-        </thead>
-        <tbody>
-	        {# Bill items#}
-            {% for vm in vms %}
+                </address>
+            </div>
+        </div>
+        <hr>
+        <table class="table table-bordered"> 
+            {# Bill header #}
+            <thead>
                 <tr>
-                    <td>{{ vm.name }}</td>
-                    <td><span class="pull-right">{{ vm.cores }}</span></td>
-                    <td><span class="pull-right">{{ vm.memory|floatformat }} GiB </span></td>
-                    <td><span class="pull-right">{{ vm.disk_size|floatformat }} GiB </span></td>
-                    <td><span class="pull-right">{{ vm.price|floatformat }} CHF</span></td>
-
+                    <th>Name</th>
+                    <th>Cores</th>
+                    <th>Memory</th>
+                    <th>Disk Size</th>
+                    <th>Price</th>
                 </tr>
-            {% endfor %}
-	        {# Bill total#}
-            <tr>
-                <td colspan=4> {% trans "Total:" %} </td>
-                <td> <span class="pull-right">{{ bill.total_price}} CHF </span></td> 
-            </tr>
-        </tbody>
-    </table>
-	<hr>
-    {# Bill Footer #}
-	<div class="row">
-	    {% trans "Alles Preise in CHF mit 8% Mehrwertsteuer." %}
-	    {% trans "Betrag zahlbar innerhalb von 30 Tagen ab Rechnungseingang." %}
-	    {% trans "Kontoverbindung:" %}
-	    <div class="row">
-	    	<div class="col-sm-6">
-	    		{% trans "IBAN:" %}
-	    	</div>
-	    	<div class="col-sm-6">
-	    		{% trans "BIC:" %}
-	    	</div>
-	    </div>
-	    <div class="row">
-	    	<div class="col-sm-6">
-	    		{% trans "CH02 ............" %}
-	    	</div>
-	    	<div class="col-sm-6">
-	    		{% trans "POFICHBEXXX" %}
-	    	</div>
-	    </div>
-	</div>
+            </thead>
+            <tbody>
+                {# Bill items#}
+                {% for vm in vms %}
+                    <tr>
+                        <td>{{ vm.name }}</td>
+                        <td><span class="pull-right">{{ vm.cores }}</span></td>
+                        <td><span class="pull-right">{{ vm.memory|floatformat }} GiB </span></td>
+                        <td><span class="pull-right">{{ vm.disk_size|floatformat }} GiB </span></td>
+                        <td><span class="pull-right">{{ vm.price|floatformat }} CHF</span></td>
+
+                    </tr>
+                {% endfor %}
+                {# Bill total#}
+                <tr>
+                    <td colspan=4> {% trans "Total:" %} </td>
+                    <td> <span class="pull-right">{{ bill.total_price}} CHF </span></td> 
+                </tr>
+            </tbody>
+        </table>
+        <hr>
+        {# Bill Footer #}
+        <div class="row">
+            {% trans "Alles Preise in CHF mit 8% Mehrwertsteuer." %}
+            {% trans "Betrag zahlbar innerhalb von 30 Tagen ab Rechnungseingang." %}
+            {% trans "Kontoverbindung:" %}
+            <div class="row">
+                <div class="col-sm-6">
+                    {% trans "IBAN:" %}
+                </div>
+                <div class="col-sm-6">
+                    {% trans "BIC:" %}
+                </div>
+            </div>
+            <div class="row">
+                <div class="col-sm-6">
+                    {% trans "CH02 ............" %}
+                </div>
+                <div class="col-sm-6">
+                    {% trans "POFICHBEXXX" %}
+                </div>
+            </div>
+        </div>
+    <div>
 </div>
 {% endblock %}
 			

From ae69a56ab2705e604c23f4155297a999a9ed7b88 Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Mon, 8 May 2017 02:47:38 +0200
Subject: [PATCH 10/15] Add error handling

---
 hosting/models.py | 37 +++++++++++++++++++++++++------------
 1 file changed, 25 insertions(+), 12 deletions(-)

diff --git a/hosting/models.py b/hosting/models.py
index 63bf8654..83bebdef 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -1,4 +1,5 @@
 import os
+import socket
 
 import oca
 from django.db import models
@@ -13,6 +14,8 @@ from membership.models import StripeCustomer, CustomUser
 from utils.models import BillingAddress
 from utils.mixins import AssignPermissionsMixin
 from .managers import VMPlansManager
+from oca.exceptions import OpenNebulaException
+from oca.pool import WrongNameError
 
 
 class VirtualMachineType(models.Model):
@@ -273,21 +276,31 @@ class HostingBill(AssignPermissionsMixin, models.Model):
         user_email = self.customer.user.email
 
         # Connect to open nebula server
-        # TODO: handle potential connection error
-        client = oca.Client("{0}:{1}".format(settings.OPENNEBULA_USERNAME,
-                                             settings.OPENNEBULA_PASSWORD),
-                            "{protocol}://{domain}:{port}{endpoint}".format(
-                                protocol=settings.OPENNEBULA_PROTOCOL,
-                                domain=settings.OPENNEBULA_DOMAIN,
-                                port=settings.OPENNEBULA_PORT,
-                                endpoint=settings.OPENNEBULA_ENDPOINT
-                            ))
+        try:
+            client = oca.Client("{0}:{1}".format(settings.OPENNEBULA_USERNAME,
+                                                 settings.OPENNEBULA_PASSWORD),
+                                "{protocol}://{domain}:{port}{endpoint}".format(
+                                    protocol=settings.OPENNEBULA_PROTOCOL,
+                                    domain=settings.OPENNEBULA_DOMAIN,
+                                    port=settings.OPENNEBULA_PORT,
+                                    endpoint=settings.OPENNEBULA_ENDPOINT
+                                ))
+        except OpenNebulaException as err:
+            logger.error("Error : {0}".format(err))
+            return None
+        except socket.timeout:
+            logger.error("Socket timeout error.")
+            return None
+
+
         # Get open nebula user id for given email
         user_pool = oca.UserPool(client)
         user_pool.info()
-        # TODO: handle potential name error
-        user_id = user_pool.get_by_name(user_email).id
-
+        try:
+            user_id = user_pool.get_by_name(user_email).id
+        except WrongNameError as wrong_name_err:
+            logger.error("User {0} does not exist.", user_email)
+            return None
         # Get vm_pool for given user_id
         vm_pool = oca.VirtualMachinePool(client)
         vm_pool.info(filter=user_id)

From c745a8c40261d61de4e3912bb84704c7774c77aa Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Mon, 8 May 2017 16:00:42 +0200
Subject: [PATCH 11/15] Remove comment

I assume that only clients are StripeCustomer objects
---
 hosting/views.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/hosting/views.py b/hosting/views.py
index 376cda00..70f1b113 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -499,7 +499,6 @@ class HostingBillListView(LoginRequiredMixin, ListView):
     model = StripeCustomer
     paginate_by = 10
     ordering = '-id'
-    #TODO show only clients i.e. get_query_set
 
 class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, DetailView):
     template_name = "hosting/bill_detail.html"

From 38b2aa8986e0ba7419086a637bd3266fa9b2a78f Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Tue, 9 May 2017 04:40:41 +0200
Subject: [PATCH 12/15] Create HostingBill obj when payment's successful

---
 hosting/views.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/hosting/views.py b/hosting/views.py
index 2bb21af0..ca1e854c 100644
--- a/hosting/views.py
+++ b/hosting/views.py
@@ -321,6 +321,8 @@ class PaymentVMView(LoginRequiredMixin, FormView):
             # Create a Hosting Order
             order = HostingOrder.create(vm_plan=plan, customer=customer,
                                         billing_address=billing_address)
+            # Create a Hosting Bill
+            bill = HostingBill.create(customer=customer, billing_address=billing_address)
 
             # Make stripe charge to a customer
             stripe_utils = StripeUtils()

From 7010c76c728a0cd8c2e4bcc456bc44392b0ddbc2 Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Tue, 9 May 2017 04:41:35 +0200
Subject: [PATCH 13/15] Add floatformat

---
 hosting/templates/hosting/bill_detail.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hosting/templates/hosting/bill_detail.html b/hosting/templates/hosting/bill_detail.html
index 449fa1bb..2d7f04b4 100644
--- a/hosting/templates/hosting/bill_detail.html
+++ b/hosting/templates/hosting/bill_detail.html
@@ -58,7 +58,7 @@
                 {# Bill total#}
                 <tr>
                     <td colspan=4> {% trans "Total:" %} </td>
-                    <td> <span class="pull-right">{{ bill.total_price}} CHF </span></td> 
+                    <td> <span class="pull-right">{{ bill.total_price|floatformat}} CHF </span></td> 
                 </tr>
             </tbody>
         </table>

From 6dedf3693e5d12d07661d4c14cd1dccb5c18bd9e Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Tue, 9 May 2017 04:41:45 +0200
Subject: [PATCH 14/15] Change to use OpenNebulaManager

---
 hosting/models.py | 66 ++++++++++-------------------------------------
 1 file changed, 13 insertions(+), 53 deletions(-)

diff --git a/hosting/models.py b/hosting/models.py
index 4a15789e..635b15a0 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -311,67 +311,27 @@ class HostingBill(AssignPermissionsMixin, models.Model):
     def __str__(self):
         return "%s" % (self.customer.user.email)
 
+    @classmethod
+    def create(cls, customer=None, billing_address=None):
+        instance = cls.objects.create(customer=customer, billing_address=billing_address)
+        return instance
+
     def get_vms(self):
-        # Get User
-        user_email = self.customer.user.email
+        email = self.customer.user.email
+        # Get opennebula client
+        opennebula_client = OpenNebulaManager()
 
-        # Connect to open nebula server
-        try:
-            client = oca.Client("{0}:{1}".format(settings.OPENNEBULA_USERNAME,
-                                                 settings.OPENNEBULA_PASSWORD),
-                                "{protocol}://{domain}:{port}{endpoint}".format(
-                                    protocol=settings.OPENNEBULA_PROTOCOL,
-                                    domain=settings.OPENNEBULA_DOMAIN,
-                                    port=settings.OPENNEBULA_PORT,
-                                    endpoint=settings.OPENNEBULA_ENDPOINT
-                                ))
-        except OpenNebulaException as err:
-            logger.error("Error : {0}".format(err))
-            return None
-        except socket.timeout:
-            logger.error("Socket timeout error.")
-            return None
-
-
-        # Get open nebula user id for given email
-        user_pool = oca.UserPool(client)
-        user_pool.info()
-        try:
-            user_id = user_pool.get_by_name(user_email).id
-        except WrongNameError as wrong_name_err:
-            logger.error("User {0} does not exist.", user_email)
-            return None
-        # Get vm_pool for given user_id
-        vm_pool = oca.VirtualMachinePool(client)
-        vm_pool.info(filter=user_id)
+        # Get vm pool
+        vm_pool = opennebula_client.get_vms(email)
 
         # Reset total price
         self.total_price = 0
         vms = []
         # Add vm in vm_pool to context
         for vm in vm_pool:
-            name = vm.name
-            cores = int(vm.template.vcpu)
-            memory = int(vm.template.memory) / 1024
-            # Check if vm has more than one disk
-            if 'DISK' in vm.template.multiple:
-                disk_size = 0
-                for disk in vm.template.disks:
-                    disk_size += int(disk.size) / 1024
-            else:
-                disk_size = int(vm.template.disk.size) / 1024
-
-            #TODO: Replace with vm plan
-            price = 0.6 * disk_size + 2 * memory + 5 * cores
-            vm = {}
-            vm['name'] = name
-            vm['price'] = price
-            vm['disk_size'] = disk_size
-            vm['cores'] = cores
-            vm['memory'] = memory
-            vms.append(vm)
-            self.total_price += price
-
+            vm_data = OpenNebulaManager.parse_vm(vm)
+            self.total_price += vm_data['price']
+            vms.append(vm_data)
         self.save()
         return vms
 

From 63052df6c9a45379480903765d685ab88344baca Mon Sep 17 00:00:00 2001
From: Modulos <modulos@protonmail.com>
Date: Tue, 9 May 2017 05:05:58 +0200
Subject: [PATCH 15/15] Change to new OpenNebula functions

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

diff --git a/hosting/models.py b/hosting/models.py
index 37b18379..b59b7f5a 100644
--- a/hosting/models.py
+++ b/hosting/models.py
@@ -339,7 +339,7 @@ class HostingBill(AssignPermissionsMixin, models.Model):
     def get_vms(self):
         email = self.customer.user.email
         # Get opennebula client
-        opennebula_client = OpenNebulaManager()
+        opennebula_client = OpenNebulaManager(create_user=False)
 
         # Get vm pool
         vm_pool = opennebula_client.get_vms(email)